mirror of
https://github.com/python/cpython.git
synced 2026-08-02 23:25:41 -04:00
* gh-153056: Fix a data race compiling the string.Template pattern in free-threading builds Template compiles its substitution pattern lazily and caches it on the class. On the free-threaded build two concurrent first uses could race: a thread that observed the pattern another thread had just compiled would try to recompile it, and re.compile() rejects flags on an already-compiled pattern, raising a spurious ValueError. Return the already-compiled pattern instead. As a side effect, a subclass that supplies an already-compiled pattern now works too; previously it raised the same ValueError at class definition. * Trim test comments and NEWS wording * Document that the pattern attribute accepts a string or a compiled regex * Comment the three states of pattern and note the documented-behavior fix in NEWS * Update Doc/library/string.rst --------- Co-authored-by: Barry Warsaw <[email protected]>
36 lines
1.1 KiB
Python
36 lines
1.1 KiB
Python
import string
|
|
import unittest
|
|
from string import Template
|
|
|
|
from test.support import threading_helper
|
|
|
|
|
|
@threading_helper.requires_working_threading()
|
|
class TestTemplateCompileRace(unittest.TestCase):
|
|
def test_concurrent_first_use(self):
|
|
# Racing the lazy pattern compile must not raise a spurious ValueError
|
|
# from recompiling an already-compiled pattern. A throwaway subclass,
|
|
# re-armed to the sentinel each round, keeps string.Template unmutated
|
|
# (subclasses precompile eagerly in __init_subclass__).
|
|
uncompiled = string._TemplatePattern
|
|
errors = []
|
|
|
|
def use_template(cls):
|
|
try:
|
|
cls("$x and ${y}").substitute(x=1, y=2)
|
|
except Exception as e:
|
|
errors.append(e)
|
|
|
|
for _ in range(20):
|
|
class T(Template):
|
|
pass
|
|
T.pattern = uncompiled
|
|
T.flags = None
|
|
threading_helper.run_concurrently(use_template, nthreads=10, args=(T,))
|
|
|
|
self.assertEqual(errors, [], msg=f"unexpected errors: {errors}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|