mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
[3.13] gh-141473: Fix subprocess.Popen.communicate to send input to stdin upon a subsequent post-timeout call (GH-141477) (#142060)
* gh-141473: Fix subprocess.Popen.communicate to send input to stdin upon a subsequent post-timeout call (GH-141477)
* gh-141473: Fix subprocess.Popen.communicate to send input to stdin
* Docs: Clarify that `input` is one time only on `communicate()`
* NEWS entry
* Add a regression test.
---------
(cherry picked from commit 526d7a8bb4)
Co-authored-by: Artur Jamro <artur.jamro@gmail.com>
Co-authored-by: Gregory P. Smith <greg@krypto.org>
* no assertStartsWith
---------
Co-authored-by: Artur Jamro <artur.jamro@gmail.com>
Co-authored-by: Gregory P. Smith <greg@krypto.org>
This commit is contained in:
parent
704bb69bd8
commit
2f3024f066
4 changed files with 42 additions and 2 deletions
|
|
@ -831,7 +831,9 @@ Instances of the :class:`Popen` class have the following methods:
|
|||
|
||||
If the process does not terminate after *timeout* seconds, a
|
||||
:exc:`TimeoutExpired` exception will be raised. Catching this exception and
|
||||
retrying communication will not lose any output.
|
||||
retrying communication will not lose any output. Supplying *input* to a
|
||||
subsequent post-timeout :meth:`communicate` call is in undefined behavior
|
||||
and may become an error in the future.
|
||||
|
||||
The child process is not killed if the timeout expires, so in order to
|
||||
cleanup properly a well-behaved application should kill the child process and
|
||||
|
|
|
|||
|
|
@ -2111,7 +2111,7 @@ def _communicate(self, input, endtime, orig_timeout):
|
|||
input_view = self._input.cast("b") # byte input required
|
||||
|
||||
with _PopenSelector() as selector:
|
||||
if self.stdin and input:
|
||||
if self.stdin and not self.stdin.closed and self._input:
|
||||
selector.register(self.stdin, selectors.EVENT_WRITE)
|
||||
if self.stdout and not self.stdout.closed:
|
||||
selector.register(self.stdout, selectors.EVENT_READ)
|
||||
|
|
|
|||
|
|
@ -1684,6 +1684,40 @@ def test_wait_negative_timeout(self):
|
|||
|
||||
self.assertEqual(proc.wait(), 0)
|
||||
|
||||
def test_post_timeout_communicate_sends_input(self):
|
||||
"""GH-141473 regression test; the stdin pipe must close"""
|
||||
with subprocess.Popen(
|
||||
[sys.executable, "-uc", """\
|
||||
import sys
|
||||
while c := sys.stdin.read(512):
|
||||
sys.stdout.write(c)
|
||||
print()
|
||||
"""],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
) as proc:
|
||||
try:
|
||||
data = f"spam{'#'*4096}beans"
|
||||
proc.communicate(
|
||||
input=data,
|
||||
timeout=0,
|
||||
)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
pass
|
||||
# Prior to the bugfix, this would hang as the stdin
|
||||
# pipe to the child had not been closed.
|
||||
try:
|
||||
stdout, stderr = proc.communicate(timeout=15)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
self.fail("communicate() hung waiting on child process that should have seen its stdin pipe close and exit")
|
||||
self.assertEqual(
|
||||
proc.returncode, 0,
|
||||
msg=f"STDERR:\n{stderr}\nSTDOUT:\n{stdout}")
|
||||
self.assertTrue(stdout.startswith("spam"), msg=stdout)
|
||||
self.assertIn("beans", stdout)
|
||||
|
||||
|
||||
class RunFuncTestCase(BaseTestCase):
|
||||
def run_python(self, code, **kwargs):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
When :meth:`subprocess.Popen.communicate` was called with *input* and a
|
||||
*timeout* and is called for a second time after a
|
||||
:exc:`~subprocess.TimeoutExpired` exception before the process has died, it
|
||||
should no longer hang.
|
||||
Loading…
Add table
Add a link
Reference in a new issue