Skip to content

Commit a05b9cd

Browse files
authored
gh-151179: Fix pidfd leak in asyncio _PidfdChildWatcher (#151186)
1 parent 72e7edd commit a05b9cd

3 files changed

Lines changed: 45 additions & 2 deletions

File tree

Lib/asyncio/unix_events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -889,8 +889,8 @@ def _do_wait(self, pid, pidfd, callback, args):
889889
pid)
890890
else:
891891
returncode = waitstatus_to_exitcode(status)
892-
893-
os.close(pidfd)
892+
finally:
893+
os.close(pidfd)
894894
callback(pid, returncode, *args)
895895

896896
class _ThreadedChildWatcher:

Lib/test/test_asyncio/test_unix_events.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,5 +1333,45 @@ async def child_main():
13331333

13341334
self.assertEqual(result.value, 0)
13351335

1336+
1337+
@unittest.skipUnless(
1338+
unix_events.can_use_pidfd(),
1339+
"operating system does not support pidfd",
1340+
)
1341+
class PidfdChildWatcherTests(test_utils.TestCase):
1342+
1343+
def setUp(self):
1344+
super().setUp()
1345+
self.loop = asyncio.new_event_loop()
1346+
self.set_event_loop(self.loop)
1347+
1348+
def test_pidfd_closed_when_waitpid_raises(self):
1349+
# _do_wait() must close the pidfd even when waitpid()
1350+
# fails with something other than ChildProcessError, otherwise the
1351+
# pidfd is leaked
1352+
self.loop.set_exception_handler(lambda loop, context: None)
1353+
1354+
async def coro():
1355+
before = os_helper.fd_count()
1356+
proc = await asyncio.create_subprocess_exec(
1357+
sys.executable, '-c', 'import sys; sys.stdin.read()',
1358+
stdin=asyncio.subprocess.PIPE
1359+
)
1360+
1361+
with mock.patch.object(os, 'waitpid',
1362+
side_effect=OSError('unexpected')) as m:
1363+
proc.stdin.close()
1364+
while not m.called:
1365+
await asyncio.sleep(0)
1366+
1367+
os.waitpid(proc.pid, 0)
1368+
proc._transport._process_exited(0)
1369+
await proc.wait()
1370+
1371+
self.assertEqual(os_helper.fd_count(), before)
1372+
1373+
self.loop.run_until_complete(coro())
1374+
1375+
13361376
if __name__ == '__main__':
13371377
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a pidfd leak in ``_PidfdChildWatcher`` on Linux: the watcher no
2+
longer leaks the process file descriptor when ``waitpid()`` fails with an
3+
error other than :exc:`ChildProcessError`.

0 commit comments

Comments
 (0)