[3.14] gh-139001: Fix thread-safety issue in pathlib.Path (gh-139066) (gh-139926)

Don't cache the joined path in `_raw_path` because the caching isn't thread safe.
(cherry picked from commit d9b4eef71e)

Co-authored-by: Sam Gross <colesbury@gmail.com>
This commit is contained in:
Miss Islington (bot) 2025-10-10 23:47:47 +02:00 committed by GitHub
parent 7ea79f6342
commit 2660e98b30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 25 additions and 6 deletions

View file

@ -334,13 +334,8 @@ def _raw_path(self):
return paths[0] return paths[0]
elif paths: elif paths:
# Join path segments from the initializer. # Join path segments from the initializer.
path = self.parser.join(*paths) return self.parser.join(*paths)
# Cache the joined path.
paths.clear()
paths.append(path)
return path
else: else:
paths.append('')
return '' return ''
@property @property

View file

@ -3,6 +3,8 @@
""" """
import unittest import unittest
import threading
from test.support import threading_helper
from .support import is_pypi from .support import is_pypi
from .support.lexical_path import LexicalPath from .support.lexical_path import LexicalPath
@ -158,6 +160,26 @@ def test_parts(self):
parts = p.parts parts = p.parts
self.assertEqual(parts, (sep, 'a', 'b')) 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): def test_parent(self):
# Relative # Relative
P = self.cls P = self.cls

View file

@ -0,0 +1,2 @@
Fix race condition in :class:`pathlib.Path` on the internal ``_raw_paths``
field.