diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py index 915ff845249151..b537d9bd674c0c 100644 --- a/Lib/asyncio/unix_events.py +++ b/Lib/asyncio/unix_events.py @@ -887,8 +887,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 d2b3de3b9a4cb6..b2b032b163174b 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -1333,5 +1333,44 @@ async def child_main(): self.assertEqual(result.value, 0) + +@unittest.skipUnless( + 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 + watcher = unix_events._PidfdChildWatcher() + pid = os.posix_spawn(sys.executable, [sys.executable, '-c', ''], + os.environ) + pidfd = os.pidfd_open(pid) + try: + async def coro(): + with mock.patch.object(os, 'waitpid', + side_effect=OSError('unexpected')): + with self.assertRaises(OSError): + watcher._do_wait(pid, pidfd, lambda *a: None, ()) + + self.loop.run_until_complete(coro()) + + with self.assertRaises(OSError): + os.fstat(pidfd) + finally: + try: + os.close(pidfd) + except OSError: + pass + os.waitpid(pid, 0) + + 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 00000000000000..904edf3a8c7090 --- /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`.