mirror of
https://github.com/python/cpython
synced 2026-11-13 16:06:16 +03:00
gh-153056: Fix a data race compiling the string.Template pattern in free-threading builds (GH-153057)
* 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
---------
(cherry picked from commit 45729033bf)
Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com>
Co-authored-by: Barry Warsaw <barry@python.org>
35 lines
1.1 KiB
Python
35 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()
|