]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-103847: fix some asyncio subprocess cancellation bugs (#146571)
authorKumar Aditya <kumaraditya@python.org>
Tue, 21 Jul 2026 18:15:59 +0000 (23:45 +0530)
committerGitHub <noreply@github.com>
Tue, 21 Jul 2026 18:15:59 +0000 (18:15 +0000)
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 d436512d6c45fc5458b2eed87c6f46063fb10a74..1a7d534261ed72029a9cd5a5d6fd4198b162f07c 100644 (file)
@@ -213,7 +213,7 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
             raise
         except BaseException:
             transp.close()
-            await transp._wait()
+            await tasks.shield(transp._wait())
             raise
 
         return transp
index 8241c10b32d105c03a12872dc37a49a4ad7efb5a..efc7c0c158d3905d0d7382475498ee639648edaa 100644 (file)
@@ -406,7 +406,7 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
             raise
         except BaseException:
             transp.close()
-            await transp._wait()
+            await tasks.shield(transp._wait())
             raise
 
         return transp
index ce127e73f50d8a3883800f4cf44d3184b07cbae0..f9b4fe53acb13430dd4cff399741b1d742550470 100644 (file)
@@ -12,7 +12,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")
@@ -1028,7 +1028,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()
@@ -1039,7 +1038,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()
@@ -1050,7 +1048,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()