https://github.com/python/cpython/commit/f429fb36a16134a2a0e839d0f385eb8fee280de4
commit: f429fb36a16134a2a0e839d0f385eb8fee280de4
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-07-21T18:15:59Z
summary:
gh-103847: fix some asyncio subprocess cancellation bugs (#146571)
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 d436512d6c45fc5..1a7d534261ed720 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -213,7 +213,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 8241c10b32d105c..efc7c0c158d3905 100644
--- a/Lib/asyncio/windows_events.py
+++ b/Lib/asyncio/windows_events.py
@@ -406,7 +406,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 ce127e73f50d8a3..f9b4fe53acb1343 100644
--- a/Lib/test/test_asyncio/test_subprocess.py
+++ b/Lib/test/test_asyncio/test_subprocess.py
@@ -12,7 +12,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")
@@ -1028,7 +1028,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()
@@ -1039,7 +1038,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()
@@ -1050,7 +1048,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]