Repository navigation
Closed
Labels
00 - Bug39 - free-threadingPRs and issues related to support for free-threading CPython (a.k.a. no-GIL, PEP 703)PRs and issues related to support for free-threading CPython (a.k.a. no-GIL, PEP 703)component: numpy.random
Description
Describe the issue:
This is not terribly surprising, but the attached example is a TSAN data race under NumPy 2.5.1.
While you can certainly say "you shouldn't be setting the seed concurrently from multiple threads", doing so shouldn't be a C-level race because that is UB.
Reproduce the code example:
import threading
import time
from absl.testing import absltest
import numpy as np
class TsanFailureTest(absltest.TestCase):
def test_mt19937_seed_get_state_race(self):
stop = False
def seeder():
while not stop:
np.random.seed(12345)
def getter():
while not stop:
_ = np.random.get_state()
threads = [
threading.Thread(target=seeder) for _ in range(4)
] + [
threading.Thread(target=getter) for _ in range(4)
]
for t in threads:
t.start()
time.sleep(0.5)
stop = True
for t in threads:
t.join()
if __name__ == "__main__":
absltest.main()Error message:
WARNING: ThreadSanitizer: data race (pid=8797)
Read of size 4 at 0x7f6c3c840c78 by thread T6:
#0 __pyx_pf_5numpy_6random_8_mt19937_7MT19937_5state___get__ third_party/py/numpy/_cython/random/_mt19937.c:7153:45 (_mt19937.abi3.so+0x1af2d) (BuildId: 84033ccc726a8096efee455b3a93cd98)
#1 __pyx_pw_5numpy_6random_8_mt19937_7MT19937_5state_1__get__ third_party/py/numpy/_cython/random/_mt19937.c:7066:13 (_mt19937.abi3.so+0x1af2d)
#2 __pyx_getprop_5numpy_6random_8_mt19937_7MT19937_state third_party/py/numpy/_cython/random/_mt19937.c:7838:10 (_mt19937.abi3.so+0x1af2d)
#3 getset_get third_party/python_runtime/v3_14_unstable/Objects/descrobject.c:194:16
#4 _PyObject_GenericGetAttrWithDict third_party/python_runtime/v3_14_unstable/Objects/object.c
...
Previous write of size 4 at 0x7f6c3c840c78 by thread T3:
#0 mt19937_seed third_party/py/numpy/random/src/mt19937/mt19937.c:10:21
#1 __pyx_pf_5numpy_6random_8_mt19937_7MT19937_2_legacy_seeding third_party/py/numpy/_cython/random/_mt19937.c:6356:17 (_mt19937.abi3.so+0x152fa)
#2 __pyx_pw_5numpy_6random_8_mt19937_7MT19937_3_legacy_seeding third_party/py/numpy/_cython/random/_mt19937.c:6015:13 (_mt19937.abi3.so+0x12b05)
#3 __Pyx_CyFunction_Vectorcall_FASTCALL_KEYWORDS third_party/py/numpy/_cython/random/_mt19937.c:13156:12 (_mt19937.abi3.so+0x1e5a8)
#4 _PyObject_VectorcallTstate third_party/python_runtime/v3_14_unstable/Include/internal/pycore_call.h:169:11
...Python and NumPy Versions:
Python 3.14t
Numpy 2.5.1
Runtime Environment:
Not super easy to collect from CI.
How does this issue affect you or how did you find it:
Observed in JAX TSAN CI: https://github.com/jax-ml/jax/actions/runs/29895407638/job/88844401276
Metadata
Metadata
Assignees
Labels
00 - Bug39 - free-threadingPRs and issues related to support for free-threading CPython (a.k.a. no-GIL, PEP 703)PRs and issues related to support for free-threading CPython (a.k.a. no-GIL, PEP 703)component: numpy.random
Activity
ngoldbaum commentedon Jul 22, 2026
I wonder if we should just go ahead and make the global
np.randomrandom state be stored in a context variable or a thread-local. I doubt it's ever useful fornp.random's state to be truly process-wide global given that we already have locking and it's probably a performance bottleneck to use instead of a per-thread RNG stream. The global RNG is truly global now out of a historical accident IMO.I don't know if that idea has come up before so I'm going to ping @rkern to get his opinion. FWIW I also think
random.randomRNG state in CPython should probably become thread-local because it's a performance footgun for a naive monte carlo algorithm implementation.ngoldbaum commentedon Jul 22, 2026
The global RNG state is supposed to be thread-safe though and I think I see the issue, we're accessing the RNG state in
get_stateoutside the lock. Patch incoming...rkern commentedon Jul 22, 2026
Yes, we have locks for this purpose. I suspect we looked at the the get path and assumed the GIL would handle it.
ngoldbaum commentedon Jul 22, 2026
I opened #32061