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]

Reply via email to