Skip to content

Commit 55f6f3f

Browse files
committed
[3.14] gh-151179: Fix pidfd leak in asyncio _PidfdChildWatcher (GH-151186)
(cherry picked from commit a05b9cd) Co-authored-by: Timofei <128279579+deadlovelll@users.noreply.github.com>
1 parent e0c28e2 commit 55f6f3f

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
@@ -900,8 +900,8 @@ def _do_wait(self, pid, pidfd, callback, args):
900900
pid)
901901
else:
902902
returncode = waitstatus_to_exitcode(status)
903-
904-
os.close(pidfd)
903+
finally:
904+
os.close(pidfd)
905905
callback(pid, returncode, *args)
906906

907907
class _ThreadedChildWatcher:

‎Lib/test/test_asyncio/test_unix_events.py‎

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

13291329
self.assertEqual(result.value, 0)
13301330

1331+
1332+
@unittest.skipUnless(
1333+
unix_events.can_use_pidfd(),
1334+
"operating system does not support pidfd",
1335+
)
1336+
class PidfdChildWatcherTests(test_utils.TestCase):
1337+
1338+
def setUp(self):
1339+
super().setUp()
1340+
self.loop = asyncio.new_event_loop()
1341+
self.set_event_loop(self.loop)
1342+
1343+
def test_pidfd_closed_when_waitpid_raises(self):
1344+
# _do_wait() must close the pidfd even when waitpid()
1345+
# fails with something other than ChildProcessError, otherwise the
1346+
# pidfd is leaked
1347+
self.loop.set_exception_handler(lambda loop, context: None)
1348+
1349+
async def coro():
1350+
before = os_helper.fd_count()
1351+
proc = await asyncio.create_subprocess_exec(
1352+
sys.executable, '-c', 'import sys; sys.stdin.read()',
1353+
stdin=asyncio.subprocess.PIPE
1354+
)
1355+
1356+
with mock.patch.object(os, 'waitpid',
1357+
side_effect=OSError('unexpected')) as m:
1358+
proc.stdin.close()
1359+
while not m.called:
1360+
await asyncio.sleep(0)
1361+
1362+
os.waitpid(proc.pid, 0)
1363+
proc._transport._process_exited(0)
1364+
await proc.wait()
1365+
1366+
self.assertEqual(os_helper.fd_count(), before)
1367+
1368+
self.loop.run_until_complete(coro())
1369+
1370+
13311371
if __name__ == '__main__':
13321372
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)