]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-89727: Fix FD leak on `os.fwalk()` generator finalization. (#119766)
authorBarney Gale <barney.gale@gmail.com>
Thu, 30 May 2024 03:45:47 +0000 (04:45 +0100)
committerGitHub <noreply@github.com>
Thu, 30 May 2024 03:45:47 +0000 (03:45 +0000)
Follow-up to 3c890b50. Ensure we `os.close()` open file descriptors when
the `os.fwalk()` generator is finalized.

Lib/os.py
Lib/test/test_os.py

index cef5d90a8c23dfc94ef12503330d457e950ce02c..0408e2db79e66e8b77ecd0972e9e85392b4cb0a0 100644 (file)
--- 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)
index 7dc5784820e8476185f33307f481b04e04f81db6..de5a86f676c4d5c3cb385fd2897893a145a4a8d5 100644 (file)
@@ -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