]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-103847: fix some asyncio subprocess cancellation bugs (GH-146571) (#154632)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 24 Jul 2026 16:25:46 +0000 (18:25 +0200)
committerGitHub <noreply@github.com>
Fri, 24 Jul 2026 16:25:46 +0000 (21:55 +0530)
gh-103847: fix some asyncio subprocess cancellation bugs (GH-146571)
(cherry picked from commit f429fb36a16134a2a0e839d0f385eb8fee280de4)

Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Lib/asyncio/base_subprocess.py
Lib/asyncio/unix_events.py
Lib/asyncio/windows_events.py
Lib/test/test_asyncio/test_subprocess.py

index 98e72f212aa203e8167cc1d7ceeffad2448e9e6d..0423bc100b2d932213914be5f11582ca926cdd2a 100644 (file)
@@ -22,7 +22,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
         self._proc = None
         self._pid = None
         self._returncode = None
-        self._exit_waiters = []
+        self._exit_waiters = set()
         self._pending_calls = collections.deque()
         self._pipes = {}
         self._finished = False
@@ -211,6 +211,14 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
         except (SystemExit, KeyboardInterrupt):
             raise
         except BaseException as exc:
+            # Close any pipes that were already connected before the
+            # error/cancellation to avoid leaking file descriptors.
+            for proto in self._pipes.values():
+                if proto is not None:
+                    proto.pipe.close()
+            for raw_pipe in (proc.stdin, proc.stdout, proc.stderr):
+                if raw_pipe is not None:
+                    raw_pipe.close()
             if waiter is not None and not waiter.cancelled():
                 waiter.set_exception(exc)
         else:
@@ -260,8 +268,12 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
             return self._returncode
 
         waiter = self._loop.create_future()
-        self._exit_waiters.append(waiter)
-        return await waiter
+        self._exit_waiters.add(waiter)
+        try:
+            return await waiter
+        finally:
+            if self._exit_waiters is not None:
+                self._exit_waiters.discard(waiter)
 
     def _try_finish(self):
         assert not self._finished
index af33d7ac860cf45277393280f06039827b6e016d..36de51970c6bd5b99c58b81fdad7d1386015bd75 100644 (file)
@@ -212,7 +212,7 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
             raise
         except BaseException:
             transp.close()
-            await transp._wait()
+            await tasks.shield(transp._wait())
             raise
 
         return transp
index 0bf7732136f1f8ea8ecf4da18e621acc9895be0c..c905db1016807f9d5daa8f6b5c39df8a097ac484 100644 (file)
@@ -408,7 +408,7 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
             raise
         except BaseException:
             transp.close()
-            await transp._wait()
+            await tasks.shield(transp._wait())
             raise
 
         return transp
index 4ab4315f1efe821f05cd33b639f8eb7d3e62eb9a..aab92db872d6a98b3233a4d44ed0fd5afc3b38f6 100644 (file)
@@ -11,7 +11,7 @@ from asyncio import base_subprocess
 from asyncio import subprocess
 from test.test_asyncio import utils as test_utils
 from test import support
-from test.support import os_helper, warnings_helper, gc_collect
+from test.support import os_helper, gc_collect
 
 if not support.has_subprocess_support:
     raise unittest.SkipTest("test module requires subprocess")
@@ -919,7 +919,6 @@ class SubprocessMixin:
 
         self.loop.run_until_complete(main())
 
-    @warnings_helper.ignore_warnings(category=ResourceWarning)
     def test_subprocess_read_pipe_cancelled(self):
         async def main():
             loop = asyncio.get_running_loop()
@@ -930,7 +929,6 @@ class SubprocessMixin:
         asyncio.run(main())
         gc_collect()
 
-    @warnings_helper.ignore_warnings(category=ResourceWarning)
     def test_subprocess_write_pipe_cancelled(self):
         async def main():
             loop = asyncio.get_running_loop()
@@ -941,7 +939,6 @@ class SubprocessMixin:
         asyncio.run(main())
         gc_collect()
 
-    @warnings_helper.ignore_warnings(category=ResourceWarning)
     def test_subprocess_read_write_pipe_cancelled(self):
         async def main():
             loop = asyncio.get_running_loop()