Skip to content

Commit 9e9e37b

Browse files
committed
bpo-39744: make asyncio.subprocess communicate similar to non-asyncio 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.
1 parent 1d99e9e commit 9e9e37b

File tree

4 files changed

+28
-7
lines changed

4 files changed

+28
-7
lines changed

Doc/library/asyncio-subprocess.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,9 @@ their completion.
207207
Interact with process:
208208

209209
1. send data to *stdin* (if *input* is not ``None``);
210-
2. read data from *stdout* and *stderr*, until EOF is reached;
211-
3. wait for process to terminate.
210+
2. closes *stdin*;
211+
3. read data from *stdout* and *stderr*, until EOF is reached;
212+
4. wait for process to terminate.
212213

213214
The optional *input* argument is the data (:class:`bytes` object)
214215
that will be sent to the child process.

Lib/asyncio/subprocess.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,11 @@ def kill(self):
144144

145145
async def _feed_stdin(self, input):
146146
debug = self._loop.get_debug()
147-
self.stdin.write(input)
148-
if debug:
149-
logger.debug(
150-
'%r communicate: feed stdin (%s bytes)', self, len(input))
147+
if input is not None:
148+
self.stdin.write(input)
149+
if debug:
150+
logger.debug(
151+
'%r communicate: feed stdin (%s bytes)', self, len(input))
151152
try:
152153
await self.stdin.drain()
153154
except (BrokenPipeError, ConnectionResetError) as exc:
@@ -180,7 +181,7 @@ async def _read_stream(self, fd):
180181
return output
181182

182183
async def communicate(self, input=None):
183-
if input is not None:
184+
if self.stdin is not None:
184185
stdin = self._feed_stdin(input)
185186
else:
186187
stdin = self._noop()

Lib/test/test_asyncio/test_subprocess.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,24 @@ async def run(data):
151151
self.assertEqual(exitcode, 0)
152152
self.assertEqual(stdout, b'some data')
153153

154+
def test_communicate_none_input(self):
155+
args = PROGRAM_CAT
156+
157+
async def run():
158+
proc = await asyncio.create_subprocess_exec(
159+
*args,
160+
stdin=subprocess.PIPE,
161+
stdout=subprocess.PIPE,
162+
)
163+
stdout, stderr = await proc.communicate()
164+
return proc.returncode, stdout
165+
166+
task = run()
167+
task = asyncio.wait_for(task, support.LONG_TIMEOUT)
168+
exitcode, stdout = self.loop.run_until_complete(task)
169+
self.assertEqual(exitcode, 0)
170+
self.assertEqual(stdout, b'')
171+
154172
def test_shell(self):
155173
proc = self.loop.run_until_complete(
156174
asyncio.create_subprocess_shell('exit 7')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make func:``asyncio.subprocess.Process.communicate`` close subprocess's stdin even when called with input=None

0 commit comments

Comments
 (0)