https://github.com/python/cpython/commit/99f684403a4afce526fb2258f7c5170a267b5d1a
commit: 99f684403a4afce526fb2258f7c5170a267b5d1a
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-07-24T16:50:18Z
summary:

[3.15] gh-103847: fix some asyncio subprocess cancellation bugs (GH-146571) 
(#154633)

gh-103847: fix some asyncio subprocess cancellation bugs (GH-146571)
(cherry picked from commit f429fb36a16134a2a0e839d0f385eb8fee280de4)

Co-authored-by: Kumar Aditya <[email protected]>

files:
M Lib/asyncio/base_subprocess.py
M Lib/asyncio/unix_events.py
M Lib/asyncio/windows_events.py
M Lib/test/test_asyncio/test_subprocess.py

diff --git a/Lib/asyncio/base_subprocess.py b/Lib/asyncio/base_subprocess.py
index 98e72f212aa203e..0423bc100b2d932 100644
--- a/Lib/asyncio/base_subprocess.py
+++ b/Lib/asyncio/base_subprocess.py
@@ -22,7 +22,7 @@ def __init__(self, loop, protocol, args, shell,
         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 @@ async def _connect_pipes(self, waiter):
         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 @@ async def _wait(self):
             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
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
index bb82ba5ae10f2e1..a0fc6c8e66e58ab 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -212,7 +212,7 @@ async def _make_subprocess_transport(self, protocol, args, 
shell,
             raise
         except BaseException:
             transp.close()
-            await transp._wait()
+            await tasks.shield(transp._wait())
             raise
 
         return transp
diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py
index 0bf7732136f1f8e..c905db1016807f9 100644
--- a/Lib/asyncio/windows_events.py
+++ b/Lib/asyncio/windows_events.py
@@ -408,7 +408,7 @@ async def _make_subprocess_transport(self, protocol, args, 
shell,
             raise
         except BaseException:
             transp.close()
-            await transp._wait()
+            await tasks.shield(transp._wait())
             raise
 
         return transp
diff --git a/Lib/test/test_asyncio/test_subprocess.py 
b/Lib/test/test_asyncio/test_subprocess.py
index 4ab4315f1efe821..aab92db872d6a98 100644
--- a/Lib/test/test_asyncio/test_subprocess.py
+++ b/Lib/test/test_asyncio/test_subprocess.py
@@ -11,7 +11,7 @@
 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 @@ async def main():
 
         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 @@ async def main():
         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 @@ async def main():
         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()

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to