]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-83925: Make asyncio.subprocess communicate similar to non-asyncio (#18650)
authorMarek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
Fri, 28 Apr 2023 00:30:26 +0000 (20:30 -0400)
committerGitHub <noreply@github.com>
Fri, 28 Apr 2023 00:30:26 +0000 (17:30 -0700)
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.

Doc/library/asyncio-subprocess.rst
Lib/asyncio/subprocess.py
Lib/test/test_asyncio/test_subprocess.py
Misc/NEWS.d/next/Library/2020-02-25-00-43-22.bpo-39744.hgK689.rst [new file with mode: 0644]

index 4274638c5e86255445573c36e9cf52039d45fae2..b7c83aa04c09f13df475f656a5b4c9948de25120 100644 (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.
index cd10231f710f1139590545b3b9e3bd88c12544f2..50727ca300e63ea5a5bda456475f4d95577a7b48 100644 (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()
index eba6e2d1f28f3ec4207f59ff794c49266d7ee843..eeeca40c15cd2801ed1f7243bec2b8ff3d439531 100644 (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')
diff --git a/Misc/NEWS.d/next/Library/2020-02-25-00-43-22.bpo-39744.hgK689.rst b/Misc/NEWS.d/next/Library/2020-02-25-00-43-22.bpo-39744.hgK689.rst
new file mode 100644 (file)
index 0000000..6e690f9
--- /dev/null
@@ -0,0 +1 @@
+Make :func:`asyncio.subprocess.Process.communicate` close the subprocess's stdin even when called with ``input=None``.