From 083bf3ad1795604ee833d1595f2a536b9cd57419 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 30 May 2024 06:40:21 +0200 Subject: [PATCH] [3.13] GH-89727: Fix FD leak on `os.fwalk()` generator finalization. (GH-119766) (#119767) GH-89727: Fix FD leak on `os.fwalk()` generator finalization. (GH-119766) Follow-up to 3c890b50. Ensure we `os.close()` open file descriptors when the `os.fwalk()` generator is finalized. (cherry picked from commit a5fef800d31648d19cecc240a2fa0dc71371753e) Co-authored-by: Barney Gale --- Lib/os.py | 11 +++++++++-- Lib/test/test_os.py | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index cef5d90a8c2..0408e2db79e 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -480,8 +480,15 @@ if {open, stat} <= supports_dir_fd and {scandir, stat} <= supports_fd: top = fspath(top) stack = [(_fwalk_walk, (True, dir_fd, top, top, None))] isbytes = isinstance(top, bytes) - while stack: - yield from _fwalk(stack, isbytes, topdown, onerror, follow_symlinks) + try: + while stack: + yield from _fwalk(stack, isbytes, topdown, onerror, follow_symlinks) + finally: + # Close any file descriptors still on the stack. + while stack: + action, value = stack.pop() + if action == _fwalk_close: + close(value) # Each item in the _fwalk() stack is a pair (action, args). _fwalk_walk = 0 # args: (isroot, dirfd, toppath, topname, entry) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 7dc5784820e..de5a86f676c 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1685,6 +1685,27 @@ class FwalkTests(WalkTests): self.addCleanup(os.close, newfd) self.assertEqual(newfd, minfd) + @unittest.skipIf( + support.is_emscripten, "Cannot dup stdout on Emscripten" + ) + @unittest.skipIf( + support.is_android, "dup return value is unpredictable on Android" + ) + def test_fd_finalization(self): + # Check that close()ing the fwalk() generator closes FDs + def getfd(): + fd = os.dup(1) + os.close(fd) + return fd + for topdown in (False, True): + old_fd = getfd() + it = self.fwalk(os_helper.TESTFN, topdown=topdown) + self.assertEqual(getfd(), old_fd) + next(it) + self.assertGreater(getfd(), old_fd) + it.close() + self.assertEqual(getfd(), old_fd) + # fwalk() keeps file descriptors open test_walk_many_open_files = None