Skip to content

Commit

Permalink
pythongh-83925: Make asyncio.subprocess communicate similar to non-as…
Browse files Browse the repository at this point in the history
…yncio (python#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.
  • Loading branch information
marmarek authored Apr 28, 2023
1 parent 424a785 commit 67d140d
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 7 deletions.
9 changes: 7 additions & 2 deletions Doc/library/asyncio-subprocess.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
11 changes: 6 additions & 5 deletions Lib/asyncio/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,11 @@ def kill(self):

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:
Expand Down Expand Up @@ -180,7 +181,7 @@ async def _read_stream(self, fd):
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()
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_asyncio/test_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,24 @@ async def run(data):
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')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make :func:`asyncio.subprocess.Process.communicate` close the subprocess's stdin even when called with ``input=None``.

0 comments on commit 67d140d

Please sign in to comment.