https://github.com/python/cpython/commit/a05b9cde938a56d3bcc6055aa2cbcdca6c4fdf0f
commit: a05b9cde938a56d3bcc6055aa2cbcdca6c4fdf0f
branch: main
author: Timofei <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-06-11T11:43:46Z
summary:
gh-151179: Fix pidfd leak in asyncio _PidfdChildWatcher (#151186)
files:
A Misc/NEWS.d/next/Library/2026-06-09-19-43-24.gh-issue-151179.hl_6Z0.rst
M Lib/asyncio/unix_events.py
M Lib/test/test_asyncio/test_unix_events.py
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
index 93c5802cd44460d..676e0b670faa49b 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -889,8 +889,8 @@ def _do_wait(self, pid, pidfd, callback, args):
pid)
else:
returncode = waitstatus_to_exitcode(status)
-
- os.close(pidfd)
+ finally:
+ os.close(pidfd)
callback(pid, returncode, *args)
class _ThreadedChildWatcher:
diff --git a/Lib/test/test_asyncio/test_unix_events.py
b/Lib/test/test_asyncio/test_unix_events.py
index d2b3de3b9a4cb61..118cb866997c51c 100644
--- a/Lib/test/test_asyncio/test_unix_events.py
+++ b/Lib/test/test_asyncio/test_unix_events.py
@@ -1333,5 +1333,45 @@ async def child_main():
self.assertEqual(result.value, 0)
+
[email protected](
+ unix_events.can_use_pidfd(),
+ "operating system does not support pidfd",
+)
+class PidfdChildWatcherTests(test_utils.TestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.loop = asyncio.new_event_loop()
+ self.set_event_loop(self.loop)
+
+ def test_pidfd_closed_when_waitpid_raises(self):
+ # _do_wait() must close the pidfd even when waitpid()
+ # fails with something other than ChildProcessError, otherwise the
+ # pidfd is leaked
+ self.loop.set_exception_handler(lambda loop, context: None)
+
+ async def coro():
+ before = os_helper.fd_count()
+ proc = await asyncio.create_subprocess_exec(
+ sys.executable, '-c', 'import sys; sys.stdin.read()',
+ stdin=asyncio.subprocess.PIPE
+ )
+
+ with mock.patch.object(os, 'waitpid',
+ side_effect=OSError('unexpected')) as m:
+ proc.stdin.close()
+ while not m.called:
+ await asyncio.sleep(0)
+
+ os.waitpid(proc.pid, 0)
+ proc._transport._process_exited(0)
+ await proc.wait()
+
+ self.assertEqual(os_helper.fd_count(), before)
+
+ self.loop.run_until_complete(coro())
+
+
if __name__ == '__main__':
unittest.main()
diff --git
a/Misc/NEWS.d/next/Library/2026-06-09-19-43-24.gh-issue-151179.hl_6Z0.rst
b/Misc/NEWS.d/next/Library/2026-06-09-19-43-24.gh-issue-151179.hl_6Z0.rst
new file mode 100644
index 000000000000000..904edf3a8c70908
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-09-19-43-24.gh-issue-151179.hl_6Z0.rst
@@ -0,0 +1,3 @@
+Fix a pidfd leak in ``_PidfdChildWatcher`` on Linux: the watcher no
+longer leaks the process file descriptor when ``waitpid()`` fails with an
+error other than :exc:`ChildProcessError`.
_______________________________________________
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]