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