]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-139001: Fix thread-safety issue in `pathlib.Path` (gh-139066)
authorSam Gross <colesbury@gmail.com>
Fri, 10 Oct 2025 21:20:18 +0000 (17:20 -0400)
committerGitHub <noreply@github.com>
Fri, 10 Oct 2025 21:20:18 +0000 (17:20 -0400)
Don't cache the joined path in `_raw_path` because the caching isn't thread safe.

Lib/pathlib/__init__.py
Lib/test/test_pathlib/test_join.py
Misc/NEWS.d/next/Library/2025-09-17-12-07-21.gh-issue-139001.O6tseN.rst [new file with mode: 0644]

index 6c07cd9ab010ad57bf5670e3900b35aca9ac3b46..51359cec8b0f9e28452641c9185a62d9478066e1 100644 (file)
@@ -336,13 +336,8 @@ class PurePath:
             return paths[0]
         elif paths:
             # Join path segments from the initializer.
-            path = self.parser.join(*paths)
-            # Cache the joined path.
-            paths.clear()
-            paths.append(path)
-            return path
+            return self.parser.join(*paths)
         else:
-            paths.append('')
             return ''
 
     @property
index 2f4e79345f36527d09ef611529879ff8e296e6d7..4630210e4922911f0a7e6a5127e402e1a86f73dd 100644 (file)
@@ -3,6 +3,8 @@ Tests for pathlib.types._JoinablePath
 """
 
 import unittest
+import threading
+from test.support import threading_helper
 
 from .support import is_pypi
 from .support.lexical_path import LexicalPath
@@ -158,6 +160,26 @@ class JoinTestBase:
         parts = p.parts
         self.assertEqual(parts, (sep, 'a', 'b'))
 
+    @threading_helper.requires_working_threading()
+    def test_parts_multithreaded(self):
+        P = self.cls
+
+        NUM_THREADS = 10
+        NUM_ITERS = 10
+
+        for _ in range(NUM_ITERS):
+            b = threading.Barrier(NUM_THREADS)
+            path = P('a') / 'b' / 'c' / 'd' / 'e'
+            expected = ('a', 'b', 'c', 'd', 'e')
+
+            def check_parts():
+                b.wait()
+                self.assertEqual(path.parts, expected)
+
+            threads = [threading.Thread(target=check_parts) for _ in range(NUM_THREADS)]
+            with threading_helper.start_threads(threads):
+                pass
+
     def test_parent(self):
         # Relative
         P = self.cls
diff --git a/Misc/NEWS.d/next/Library/2025-09-17-12-07-21.gh-issue-139001.O6tseN.rst b/Misc/NEWS.d/next/Library/2025-09-17-12-07-21.gh-issue-139001.O6tseN.rst
new file mode 100644 (file)
index 0000000..3ad5a12
--- /dev/null
@@ -0,0 +1,2 @@
+Fix race condition in :class:`pathlib.Path` on the internal ``_raw_paths``
+field.