Skip to content

Commit 67d140d

Browse files
authored
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.
1 parent 424a785 commit 67d140d

File tree

4 files changed

+32
-7
lines changed

4 files changed

+32
-7
lines changed

Doc/library/asyncio-subprocess.rst

Lines changed: 7 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.
@@ -229,6 +230,10 @@ their completion.
229230
Note, that the data read is buffered in memory, so do not use
230231
this method if the data size is large or unlimited.
231232

233+
.. versionchanged:: 3.12
234+
235+
*stdin* gets closed when `input=None` too.
236+
232237
.. method:: send_signal(signal)
233238

234239
Sends the signal *signal* 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 the subprocess's stdin even when called with ``input=None``.

0 commit comments

Comments
 (0)