gh-83925: Make asyncio.subprocess communicate similar to non-asyncio (#18650)

subprocess's communicate(None) closes stdin of the child process, after
sending no (extra) data. Make asyncio variant do the same.
This fixes issues with processes that waits for EOF on stdin before
continuing.
This commit is contained in:
Marek Marczykowski-Górecki 2023-04-27 20:30:26 -04:00 committed by GitHub
parent 424a785a07
commit 67d140dba7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 32 additions and 7 deletions

View file

@ -207,8 +207,9 @@ their completion.
Interact with process:
1. send data to *stdin* (if *input* is not ``None``);
2. read data from *stdout* and *stderr*, until EOF is reached;
3. wait for process to terminate.
2. closes *stdin*;
3. read data from *stdout* and *stderr*, until EOF is reached;
4. wait for process to terminate.
The optional *input* argument is the data (:class:`bytes` object)
that will be sent to the child process.
@ -229,6 +230,10 @@ their completion.
Note, that the data read is buffered in memory, so do not use
this method if the data size is large or unlimited.
.. versionchanged:: 3.12
*stdin* gets closed when `input=None` too.
.. method:: send_signal(signal)
Sends the signal *signal* to the child process.

View file

@ -144,10 +144,11 @@ class Process:
async def _feed_stdin(self, input):
debug = self._loop.get_debug()
self.stdin.write(input)
if debug:
logger.debug(
'%r communicate: feed stdin (%s bytes)', self, len(input))
if input is not None:
self.stdin.write(input)
if debug:
logger.debug(
'%r communicate: feed stdin (%s bytes)', self, len(input))
try:
await self.stdin.drain()
except (BrokenPipeError, ConnectionResetError) as exc:
@ -180,7 +181,7 @@ class Process:
return output
async def communicate(self, input=None):
if input is not None:
if self.stdin is not None:
stdin = self._feed_stdin(input)
else:
stdin = self._noop()

View file

@ -151,6 +151,24 @@ class SubprocessMixin:
self.assertEqual(exitcode, 0)
self.assertEqual(stdout, b'some data')
def test_communicate_none_input(self):
args = PROGRAM_CAT
async def run():
proc = await asyncio.create_subprocess_exec(
*args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
stdout, stderr = await proc.communicate()
return proc.returncode, stdout
task = run()
task = asyncio.wait_for(task, support.LONG_TIMEOUT)
exitcode, stdout = self.loop.run_until_complete(task)
self.assertEqual(exitcode, 0)
self.assertEqual(stdout, b'')
def test_shell(self):
proc = self.loop.run_until_complete(
asyncio.create_subprocess_shell('exit 7')

View file

@ -0,0 +1 @@
Make :func:`asyncio.subprocess.Process.communicate` close the subprocess's stdin even when called with ``input=None``.