Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5b1862b

Browse files
gpsheadclaude
andauthored
gh-87512: Fixsubprocess usingtimeout= on Windows blocking with a largeinput= (GH-142058)
On Windows, Popen._communicate() previously wrote to stdin synchronously, which could block indefinitely if the subprocess didn't consume input= quickly and the pipe buffer filled up. The timeout= parameter was only checked when joining the reader threads, not during the stdin write.This change moves the Windows stdin writing to a background thread (similar to how stdout/stderr are read in threads), allowing the timeout to be properly enforced. If timeout expires, TimeoutExpired is raised promptly and the writer thread continues in the background. Subsequent calls to communicate() will join the existing writer thread.Adds test_communicate_timeout_large_input to verify that TimeoutExpired is raised promptly when communicate() is called with large input and a timeout, even when the subprocess doesn't consume stdin quickly.This test already passed on POSIX (where select() is used) but failed on Windows where the stdin write blocks without checking the timeout.Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent923056b commit5b1862b

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

‎Lib/subprocess.py‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,10 @@ def _readerthread(self, fh, buffer):
16131613
fh.close()
16141614

16151615

1616+
def_writerthread(self,input):
1617+
self._stdin_write(input)
1618+
1619+
16161620
def_communicate(self,input,endtime,orig_timeout):
16171621
# Start reader threads feeding into a list hanging off of this
16181622
# object, unless they've already been started.
@@ -1631,8 +1635,23 @@ def _communicate(self, input, endtime, orig_timeout):
16311635
self.stderr_thread.daemon=True
16321636
self.stderr_thread.start()
16331637

1634-
ifself.stdin:
1635-
self._stdin_write(input)
1638+
# Start writer thread to send input to stdin, unless already
1639+
# started. The thread writes input and closes stdin when done,
1640+
# or continues in the background on timeout.
1641+
ifself.stdinandnothasattr(self,"_stdin_thread"):
1642+
self._stdin_thread= \
1643+
threading.Thread(target=self._writerthread,
1644+
args=(input,))
1645+
self._stdin_thread.daemon=True
1646+
self._stdin_thread.start()
1647+
1648+
# Wait for the writer thread, or time out. If we time out, the
1649+
# thread remains writing and the fd left open in case the user
1650+
# calls communicate again.
1651+
ifhasattr(self,"_stdin_thread"):
1652+
self._stdin_thread.join(self._remaining_time(endtime))
1653+
ifself._stdin_thread.is_alive():
1654+
raiseTimeoutExpired(self.args,orig_timeout)
16361655

16371656
# Wait for the reader threads, or time out. If we time out, the
16381657
# threads remain reading and the fds left open in case the user

‎Lib/test/test_subprocess.py‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,62 @@ def test_communicate_timeout_large_output(self):
10341034
(stdout,_)=p.communicate()
10351035
self.assertEqual(len(stdout),4*64*1024)
10361036

1037+
deftest_communicate_timeout_large_input(self):
1038+
# Test that timeout is enforced when writing large input to a
1039+
# slow-to-read subprocess, and that partial input is preserved
1040+
# for continuation after timeout (gh-141473).
1041+
#
1042+
# This is a regression test for Windows matching POSIX behavior.
1043+
# On POSIX, select() is used to multiplex I/O with timeout checking.
1044+
# On Windows, stdin writing must also honor the timeout rather than
1045+
# blocking indefinitely when the pipe buffer fills.
1046+
1047+
# Input larger than typical pipe buffer (4-64KB on Windows)
1048+
input_data=b"x"* (128*1024)
1049+
1050+
p=subprocess.Popen(
1051+
[sys.executable,"-c",
1052+
"import sys, time; "
1053+
"time.sleep(30); "# Don't read stdin for a long time
1054+
"sys.stdout.buffer.write(sys.stdin.buffer.read())"],
1055+
stdin=subprocess.PIPE,
1056+
stdout=subprocess.PIPE,
1057+
stderr=subprocess.PIPE)
1058+
1059+
try:
1060+
timeout=0.2
1061+
start=time.monotonic()
1062+
try:
1063+
p.communicate(input_data,timeout=timeout)
1064+
# If we get here without TimeoutExpired, the timeout was ignored
1065+
elapsed=time.monotonic()-start
1066+
self.fail(
1067+
f"TimeoutExpired not raised. communicate() completed in "
1068+
f"{elapsed:.2f}s, but subprocess sleeps for 30s. "
1069+
"Stdin writing blocked without enforcing timeout.")
1070+
exceptsubprocess.TimeoutExpired:
1071+
elapsed=time.monotonic()-start
1072+
1073+
# Timeout should occur close to the specified timeout value,
1074+
# not after waiting for the subprocess to finish sleeping.
1075+
# Allow generous margin for slow CI, but must be well under
1076+
# the subprocess sleep time.
1077+
self.assertLess(elapsed,5.0,
1078+
f"TimeoutExpired raised after{elapsed:.2f}s; expected ~{timeout}s. "
1079+
"Stdin writing blocked without checking timeout.")
1080+
1081+
# After timeout, continue communication. The remaining input
1082+
# should be sent and we should receive all data back.
1083+
stdout,stderr=p.communicate()
1084+
1085+
# Verify all input was eventually received by the subprocess
1086+
self.assertEqual(len(stdout),len(input_data),
1087+
f"Expected{len(input_data)} bytes output but got{len(stdout)}")
1088+
self.assertEqual(stdout,input_data)
1089+
finally:
1090+
p.kill()
1091+
p.wait()
1092+
10371093
# Test for the fd leak reported in http://bugs.python.org/issue2791.
10381094
deftest_communicate_pipe_fd_leak(self):
10391095
forstdin_pipein (False,True):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix:func:`subprocess.Popen.communicate` timeout handling on Windows
2+
when writing large input. Previously, the timeout was ignored during
3+
stdin writing, causing the method to block indefinitely if the child
4+
process did not consume input quickly. The stdin write is now performed
5+
in a background thread, allowing the timeout to be properly enforced.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp