You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[subprocess] run() sometimes ignores timeout in Windows #87512
subprocess.run() handles TimeoutExpired by terminating the process and waiting on it. On POSIX, the exception object contains the partially read stdout and stderr bytes. For example:
On Windows, subprocess.run() has to finish reading output with a second communicate() call, after which it manually sets the exception's stdout and stderr attributes.
The poses the problem that the second communicate() call may block indefinitely, even though the child process has terminated.
The primary issue is that the pipe handles may be inherited by one or more descendant processes (e.g. via shell=True), which are all regarded as potential writers that keep the pipe from closing. Reading from an open pipe that's empty will block until data becomes available. This is generally desirable for efficiency, compared to polling in a loop. But in this case, the downside is that run() in Windows will effectively ignore the given timeout.
Another problem is that _communicate() writes the input to stdin on the calling thread with a single write() call. If the input exceeds the pipe capacity (4 KiB by default -- but a pipesize 'suggested' size could be supported), the write will block until the child process reads the excess data. This could block indefinitely, which will effectively ignore a given timeout. The POSIX implementation, in contrast, correctly handles a timeout in this case.
Also, Popen.__exit__() closes the stdout, stderr, and stdin files without regard to the _communicate() worker threads. This may seem innocuous, but if a worker thread is blocked on synchronous I/O with one of these files, WinAPI CloseHandle() will also block if it's closing the last handle for the file in the current process. (In this case, the kernel I/O manager has a close procedure that waits to acquire the file for the current thread before performing various housekeeping operations, primarily in the filesystem, such as clearing byte-range locks set by the current process.) A blocked close() is easy to demonstrate. For example:
args='python -c "import time; time.sleep(99)"'p=subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
try: p.communicate(timeout=1)
except: passp.kill() # terminates the shell process -- not python.exewithp: pass# stdout.close() blocks until python.exe exits
The Windows implementation of Popen._communicate() could be redesigned as follows:
* read in chunks, with a size from 1 byte up to the maximum available,
as determined by `_winapi.PeekNamedPipe()`
* write to the child's `stdin` on a separate thread
* after `communicate()` has started, ensure that synchronous I/O in worker
threads has been canceled via `CancelSynchronousIo()` before closing
the pipes.
The _winapi module would need to wrap OpenThread() and CancelSynchronousIo(), plus define the TERMINATE_THREAD (0x0001) access right.
With the proposed changes, subprocess.run() would no longer special case TimeoutExpired on Windows.
changed the title [-]subprocess.run() sometimes ignores timeout in Windows[/-][+][subprocess] run() sometimes ignores timeout in Windows[/+]on Aug 8, 2022
Activity
eryksun commentedon Feb 28, 2021
subprocess.run()handlesTimeoutExpiredby terminating the process and waiting on it. On POSIX, the exception object contains the partially readstdoutandstderrbytes. For example:On Windows,
subprocess.run()has to finish reading output with a secondcommunicate()call, after which it manually sets the exception'sstdoutandstderrattributes.The poses the problem that the second
communicate()call may block indefinitely, even though the child process has terminated.The primary issue is that the pipe handles may be inherited by one or more descendant processes (e.g. via
shell=True), which are all regarded as potential writers that keep the pipe from closing. Reading from an open pipe that's empty will block until data becomes available. This is generally desirable for efficiency, compared to polling in a loop. But in this case, the downside is thatrun()in Windows will effectively ignore the given timeout.Another problem is that
_communicate()writes the input tostdinon the calling thread with a singlewrite()call. If the input exceeds the pipe capacity (4 KiB by default -- but apipesize'suggested' size could be supported), the write will block until the child process reads the excess data. This could block indefinitely, which will effectively ignore a given timeout. The POSIX implementation, in contrast, correctly handles a timeout in this case.Also,
Popen.__exit__()closes thestdout,stderr, andstdinfiles without regard to the_communicate()worker threads. This may seem innocuous, but if a worker thread is blocked on synchronous I/O with one of these files, WinAPICloseHandle()will also block if it's closing the last handle for the file in the current process. (In this case, the kernel I/O manager has a close procedure that waits to acquire the file for the current thread before performing various housekeeping operations, primarily in the filesystem, such as clearing byte-range locks set by the current process.) A blockedclose()is easy to demonstrate. For example:The Windows implementation of
Popen._communicate()could be redesigned as follows:The
_winapimodule would need to wrapOpenThread()andCancelSynchronousIo(), plus define theTERMINATE_THREAD(0x0001) access right.With the proposed changes,
subprocess.run()would no longer special caseTimeoutExpiredon Windows.eryksun commentedon Feb 28, 2021
Demo
Popen()methods, for discussion:[-]subprocess.run() sometimes ignores timeout in Windows[/-][+][subprocess] run() sometimes ignores timeout in Windows[/+]2 remaining items
pythongh-87512: Fix subprocess communicate() timeout on Windows for l…
Add NEWS entry for pythongh-87512
pythongh-87512: Fix subprocess communicate() timeout on Windows for l…
Add NEWS entry for pythongh-87512
gh-87512: Fix `subprocess` using `timeout=` on Windows blocking with …
pythongh-87512: Fix `subprocess` using `timeout=` on Windows blocking…
pythongh-87512: Fix `subprocess` using `timeout=` on Windows blocking…
[3.14] gh-87512: Fix `subprocess` using `timeout=` on Windows blockin…
[3.13] gh-87512: Fix `subprocess` using `timeout=` on Windows blockin…
Fix _communicate_streams_windows to avoid blocking with large input
pythongh-87512: Fix `subprocess` using `timeout=` on Windows blocking…
Merge remote-tracking branch 'upstream/main' into pythongh-87512-subp…