Skip to content

Commit c02ff70

Browse files
committed
test_thread: cover ModuleObject thread-safe registries
Two tests for the thread-safe-collections work in 0a9d482: - test_module_dunder_all_added_once asserts ModuleObject.allNames keeps add-once semantics; a torn HashSet would surface duplicates in __all__ on free-threaded builds. - test_concurrent_module_attribute_access exercises ModuleObject.cache with concurrent getattr on a CLR namespace. The old plain Dictionary threw on Add ("duplicate key") under simultaneous misses; the ConcurrentDictionary version absorbs the race. Both run on every interpreter (no @freethreaded_only) — they degenerate to single-threaded smoke checks under the GIL while the FT build actually exercises contention.
1 parent 47bcfef commit c02ff70

1 file changed

Lines changed: 36 additions & 0 deletions

File tree

‎tests/test_thread.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ def test_is_gil_enabled_attribute_present_on_3_13_plus():
9999
assert isinstance(sys._is_gil_enabled(), bool)
100100

101101

102+
def test_module_dunder_all_added_once():
103+
"""Module.__all__ adds each name exactly once.
104+
105+
Exercises ModuleObject.allNames (ConcurrentDictionary) — the per-name
106+
"have we surfaced this in __all__ yet" guard. A torn HashSet would let
107+
duplicates slip through here on free-threaded builds.
108+
"""
109+
import System
110+
111+
names = list(System.__all__)
112+
assert len(names) == len(set(names))
113+
114+
102115
def _run_in_threads(target, n_threads, *args, **kwargs):
103116
"""Run target() in n_threads threads, return results in start order, raise on first error."""
104117
results = [None] * n_threads
@@ -148,6 +161,29 @@ def access(_):
148161
assert all(_run_in_threads(access, n_threads=8))
149162

150163

164+
def test_concurrent_module_attribute_access():
165+
"""Concurrent CLR-namespace attribute access — exercises ModuleObject.cache.
166+
167+
Each lookup of `System.X` either hits ModuleObject.cache or populates it
168+
on first miss. A plain Dictionary tore on simultaneous TryGetValue/Add
169+
from multiple threads; the test reads many distinct names per worker.
170+
"""
171+
import System
172+
173+
names = (
174+
"String", "Int32", "Int64", "Double", "Boolean", "Object",
175+
"DateTime", "TimeSpan", "Type", "Array", "Console", "Math",
176+
)
177+
178+
def lookup(_):
179+
for _ in range(200):
180+
for n in names:
181+
getattr(System, n)
182+
return True
183+
184+
assert all(_run_in_threads(lookup, n_threads=8))
185+
186+
151187
@freethreaded_only
152188
def test_concurrent_clr_object_creation():
153189
"""Concurrent CLR object alloc/free — exercises reflectedObjects + loadedExtensions.

0 commit comments

Comments
 (0)