Skip to content

Commit 81a459c

Browse files
committed
gh-116738: Test re module for free threading
1 parent fee7782 commit 81a459c

File tree

2 files changed

+73
-3
lines changed

2 files changed

+73
-3
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import re
2+
import unittest
3+
4+
from test.support import threading_helper
5+
from test.support.threading_helper import run_concurrently
6+
7+
8+
NTHREADS = 10
9+
10+
11+
@threading_helper.requires_working_threading()
12+
class TestRe(unittest.TestCase):
13+
def test_pattern_sub(self):
14+
"""Pattern substitution should work across threads"""
15+
pattern = re.compile(r"\w+@\w+\.\w+")
16+
text = "e-mail: test@python.org or user@pycon.org. " * 5
17+
results = []
18+
19+
def worker():
20+
substituted = pattern.sub("(redacted)", text)
21+
results.append(substituted.count("(redacted)"))
22+
23+
run_concurrently(worker_func=worker, nthreads=NTHREADS)
24+
self.assertEqual(results, [2 * 5] * NTHREADS)
25+
26+
def test_pattern_search(self):
27+
"""Pattern search should work across threads."""
28+
emails = ["alice@python.org", "bob@pycon.org"] * 10
29+
pattern = re.compile(r"\w+@\w+\.\w+")
30+
results = []
31+
32+
def worker():
33+
matches = [pattern.search(e).group() for e in emails]
34+
results.append(len(matches))
35+
36+
run_concurrently(worker_func=worker, nthreads=NTHREADS)
37+
self.assertEqual(results, [2 * 10] * NTHREADS)
38+
39+
def test_scanner_concurrent_access(self):
40+
"""Shared scanner should reject concurrent access."""
41+
pattern = re.compile(r"\w+")
42+
scanner = pattern.scanner("word " * 10)
43+
44+
def worker():
45+
for _ in range(100):
46+
try:
47+
scanner.search()
48+
except ValueError as e:
49+
if "already executing" in str(e):
50+
pass
51+
else:
52+
raise
53+
54+
run_concurrently(worker_func=worker, nthreads=NTHREADS)
55+
# This test has no assertions. Its purpose is to catch crashes and
56+
# enable thread sanitizer to detect race conditions. While "already
57+
# executing" errors are very likely, they're not guaranteed due to
58+
# non-deterministic thread scheduling, so we can't assert errors > 0.
59+
60+
61+
if __name__ == "__main__":
62+
unittest.main()

Modules/_sre/sre.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2841,20 +2841,28 @@ scanner_dealloc(PyObject *self)
28412841
static int
28422842
scanner_begin(ScannerObject* self)
28432843
{
2844+
int result;
2845+
Py_BEGIN_CRITICAL_SECTION(self);
28442846
if (self->executing) {
28452847
PyErr_SetString(PyExc_ValueError,
28462848
"regular expression scanner already executing");
2847-
return 0;
2849+
result = 0;
28482850
}
2849-
self->executing = 1;
2850-
return 1;
2851+
else {
2852+
self->executing = 1;
2853+
result = 1;
2854+
}
2855+
Py_END_CRITICAL_SECTION();
2856+
return result;
28512857
}
28522858

28532859
static void
28542860
scanner_end(ScannerObject* self)
28552861
{
2862+
Py_BEGIN_CRITICAL_SECTION(self);
28562863
assert(self->executing);
28572864
self->executing = 0;
2865+
Py_END_CRITICAL_SECTION();
28582866
}
28592867

28602868
/*[clinic input]

0 commit comments

Comments
 (0)