GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-87512: Fix ignored timeout in subprocess.run() on Windows by serhiy-storchaka · Pull Request #155409 · python/cpython

Conversation

@serhiy-storchaka

@serhiy-storchaka serhiy-storchaka commented Aug 9, 2026

Copy link
Copy Markdown
Member

The output is now read in chunks, using PeekNamedPipe() to get the available size, so the reading threads are always blocked in a cancelable read. The pending I/O is canceled before closing the pipes and after the timeout in run().

As a result, the output read before the timeout is now set on the TimeoutExpired exception, as on other platforms.

Added _winapi.OpenThread(), _winapi.CancelSynchronousIo() and _winapi.THREAD_TERMINATE, as suggested by @eryksun.

Note that _communicate() now decodes the output at the end, as the POSIX implementation does, because the reading threads no longer read via the TextIOWrapper.

  • Issue: [subprocess] run() sometimes ignores timeout in Windows #87512

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@serhiy-storchaka
pythongh-87512: Fix ignored timeout in subprocess.run() on Windows
4bdc075
The pipes can be inherited by processes which outlive the child process,
so reading them blocked until all of them exited, ignoring the timeout.

The output is now read in chunks and the pending I/O is canceled, so the
output read before the timeout is set on the TimeoutExpired exception, as
on other platforms.

Add _winapi.OpenThread(), _winapi.CancelSynchronousIo() and
_winapi.THREAD_TERMINATE.
@serhiy-storchaka
serhiy-storchaka requested a review from gpshead as a code owner August 9, 2026 08:58
@bedevere-app bedevere-app Bot mentioned this pull request Aug 9, 2026
[subprocess] run() sometimes ignores timeout in Windows #87512
Open
@bedevere-app bedevere-app Bot added the awaiting core review label Aug 9, 2026
@serhiy-storchaka
serhiy-storchaka requested a review from eryksun August 9, 2026 08:58
@read-the-docs-community

read-the-docs-community Bot commented Aug 9, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #34543980 | 📁 Comparing f48da93 against main (998b890)

  🔍 Preview build  

396 files changed · + 9 added · ± 386 modified · - 1 deleted

+ Added

  • builtins/constants.html
  • builtins/exceptions.html
  • builtins/functions.html
  • builtins/index.html
  • builtins/stdtypes.html
  • builtins/threadsafety.html
  • builtins/time-complexity.html
  • library/time-complexity.html
  • library/tkinter.dialogs.html

± Modified

  • glossary.html
  • index.html
  • license.html
  • py-modindex.html
  • c-api/arg.html
  • c-api/bool.html
  • c-api/buffer.html
  • c-api/bytearray.html
  • c-api/bytes.html
  • c-api/capsule.html
  • and 376 more...

- Deleted

  • library/dialog.html
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@serhiy-storchaka
Merge remote-tracking branch 'upstream/main' into pythongh-87512-subp…
f48da93
…rocess-timeout

# Conflicts:
#	Modules/clinic/_winapi.c.h
@kuotunyu kuotunyu mentioned this pull request Sep 23, 2026
subprocess on Windows returns None instead of raising UnicodeDecodeError #158006
Open
gpshead
gpshead reviewed Oct 12, 2026
View reviewed changes

@gpshead gpshead left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a Windows person so I'm mostly just going over this from a logical point of view and asking claude if these semantics make sense on Windows. I think this is workable but has flaws. So it still seems "better" (ie: landing it would be an improvement) but is not sufficient to fix the problem:


(claude's explanation below)

1. The cancel is one-shot and can miss. CancelSynchronousIo() only affects a thread that is inside ReadFile at that instant. If the reader is between PeekNamedPipe() and ReadFile(), or appending to the buffer, we get ERROR_NOT_FOUND (swallowed) and the thread then blocks in a read that nothing will cancel. In run() the following communicate() joins without a timeout, so we hang as before. The new tests can't hit this because the grandchild writes once and goes quiet; a grandchild that keeps writing would. I think this needs a "cancelled" flag that the reader loop checks before each read, with _cancel_io() repeating cancel + short join() until the thread exits.

2. A missed cancel in __exit__ leads to a read on a closed handle. The reader uses a raw handle captured once from get_osfhandle(). If the cancel misses, self.stdout.close() succeeds and the reader then calls ReadFile on a stale handle value: an unhandled OSError in the thread at best, a read from an unrelated reused handle at worst. The fix for 1 covers this too.

3. Could this use CancelIoEx(handle, NULL) instead? CancelSynchronousIo() is thread-targeted, so it cancels whatever synchronous I/O that thread happens to be doing (e.g. a finalizer running on it), and looking the thread up by thread.ident after is_alive() is open to thread ID reuse. My understanding is that CancelIoEx() also cancels synchronous I/O issued by other threads but is scoped to the pipe handle, which would avoid both problems and remove the need for OpenThread()/THREAD_TERMINATE. Is there a reason it doesn't work here? (It has the same miss race, so 1 still applies.)

4. Behaviour change: buffered data is dropped. fh.read() used to go through the BufferedReader/TextIOWrapper, so p.stdout.readline() followed by p.communicate() returned the rest. Reading the raw handle skips anything already buffered. POSIX already behaves this way, so this may be acceptable, but it should be a conscious decision and mentioned in the NEWS entry. [me: ugh!]

5. Is PeekNamedPipe() needed? ReadFile() on a byte-mode pipe returns as soon as any data is available, so ReadFile(handle, 32768) should give the same cancellable behaviour with half the syscalls, no peek-to-read window, and no 4K chunk cap on large outputs.

Smaller things:

  • The stdin writer path is cancelled too but has no test (input= with a full pipe). It relies on the aborted write surfacing as EINVAL so _stdin_write() swallows it, and the self.stdin.close() after it may flush and block again.
  • kill() then communicate() after a communicate(timeout=...) on a bare Popen (the pattern from the docs) still hangs, since only run() and __exit__ cancel. Fine if intentionally out of scope, but worth stating.
  • OpenThread lacks a docstring, and there are no test_winapi tests for the new functions.

Points 3 and 5 are claude's recollection of CancelIoEx and pipe ReadFile semantics - that needs verification.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@gpshead gpshead gpshead left review comments
@eryksun eryksun Awaiting requested review from eryksun

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

awaiting core review

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@serhiy-storchaka @gpshead
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.