gh-87512: Fix `subprocess` using `timeout=` on Windows blocking with … · python/cpython@5b1862b

GitHub

@@ -1034,6 +1034,62 @@ def test_communicate_timeout_large_output(self):

10341034 (stdout, _) =p.communicate()

10351035self.assertEqual(len(stdout), 4*64*1024)

103610361037+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.

10381094deftest_communicate_pipe_fd_leak(self):

10391095forstdin_pipein (False, True):