Skip to content

Commit

Permalink
bpo-39744: make asyncio.subprocess communicate similar to non-asyncio…
Browse files Browse the repository at this point in the history
… one

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 committed Apr 27, 2023
1 parent 1d99e9e commit 9e9e37b
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 7 deletions.
5 changes: 3 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 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 subprocess's stdin even when called with input=None

0 comments on commit 9e9e37b

Please sign in to comment.