Repository navigation
Description
Describe the issue:
When running the test suite under pytest-run-parallel and ThreadSanitizer, many tests cause data races. The affected tests (~80 test) are listed in this spreadsheet. Below are some tests I thought were notable.
Tests cause a crash, with no output (some of these overlap with ASan errors I found previously, and likely have to do with the tests crashing due to memory issues):
- numpy/_core/tests/test_arrayprint.py::test_multithreaded_array_printing
- numpy/_core/tests/test_multiarray.py::TestCreation::test_zeros_big
- numpy/_core/tests/test_regression.py::TestRegression::test_to_ctypes
- numpy/_core/tests/test_shape_base.py::TestConcatenate::test_huge_list_error
Segmentation fault after data race
- numpy/_core/tests/test_custom_dtypes.py::TestSFloat::test_wrapped_and_wrapped_reductions
RandomState data race (may be a linux-only bug)
- numpy/linalg/tests/test_linalg.py::TestCond::test_stacked_singular
- numpy/_core/tests/test_umath.py::TestLog::test_log_strides
- numpy/linalg/tests/test_linalg.py::TestCond::test_stacked_singular
dec_addstatus data races
- numpy/_core/tests/test_umath.py::TestRationalFunctions::test_decimal
- numpy/lib/tests/test_function_base.py::TestAverage::test_object_dtype
- numpy/lib/tests/test_function_base.py::TestAverage::test_object_no_weights
Caused test failure
- numpy/lib/tests/test_io.py::TestSaveTxt::test_large_zip
- numpy/testing/tests/test_utils.py::TestAssertNoGcCycles::test_asserts
Steps to reproduce
- Use Python
3.14twith ThreadSanitizer enabled. - Build NumPy using clang, ThreadSanitizer and without openBLAS
spin build --clean -- -Db_sanitize=thread -Dbuildtype=debugoptimized -Dallow-noblas=true
- Run test suite under
pytest-run-parallelspin test -p auto -sv
Error message:
I have the traceback files for all TSan warnings I ran into. Don't want to post them all here and make a mess, but I can provide any of them if needed.
Python and NumPy Versions:
NumPy: 2.4.0.dev0+git20251003.da5c632
Python: 3.14.0rc3+ free-threading build (heads/3.14:460ad1aa1ba, Sep 22 2025, 23:51:20) [Clang 18.1.3 (1ubuntu1)]
Runtime Environment:
[{'numpy_version': '2.4.0.dev0+git20251003.da5c632',
'python': '3.14.0rc3+ free-threading build (heads/3.14:460ad1aa1ba, Sep 22 '
'2025, 23:51:20) [Clang 18.1.3 (1ubuntu1)]',
'uname': uname_result(system='Linux', node='SPACE', release='6.6.87.2-microsoft-standard-WSL2', version='#1 SMP PREEMPT_DYNAMIC Thu Jun 5 18:30:46 UTC 2025', machine='x86_64')},
{'simd_extensions': {'baseline': ['SSE', 'SSE2', 'SSE3'],
'found': ['SSSE3',
'SSE41',
'POPCNT',
'SSE42',
'AVX',
'F16C',
'FMA3',
'AVX2'],
'not_found': ['AVX512F',
'AVX512CD',
'AVX512_KNL',
'AVX512_SKX',
'AVX512_CLX',
'AVX512_CNL',
'AVX512_ICL']}},
{'architecture': 'Haswell',
'filepath': '/usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so',
'internal_api': 'openblas',
'num_threads': 24,
'prefix': 'libopenblas',
'threading_layer': 'pthreads',
'user_api': 'blas',
'version': '0.3.26'}]
Context for the issue:
No response
Activity
Fix ThreadSanitizer data race conditions in NumPy core
kumaraditya303 commentedon Nov 17, 2025
These races are from decimal module in stdlib, linking python/cpython#141148 as the upstream tracking issue.
kumaraditya303 commentedon Nov 17, 2025
There are a few data races in hashtable implementation as well like:
From the backtrace, the data race happens in
ufunc_generic_fastcallwhich further callsPyArrayIdentityHash_GetItembut it doesn't acquire the read lock of the shared mutex like it is done inpromote_and_get_info_and_ufuncimpl_with_locking.numpy/numpy/_core/src/umath/ufunc_object.c
Lines 4338 to 4345 in 02c8c3d
numpy/numpy/_core/src/umath/dispatching.cpp
Lines 984 to 991 in 02c8c3d
ngoldbaum commentedon Nov 18, 2025
Not a data race per se, but I noticed a failure on the pytest-run-parallel CI I haven't seen before: https://github.com/numpy/numpy/actions/runs/19474581693/job/55730908994?pr=30234
That test is checking explicitly for reference leaks, so perhaps it's thread-unsafe because there's a (transient) reference from a global or something along those lines and no one noticed until now.
kumaraditya303 commentedon Nov 18, 2025
I think it is because the test relies on
break_cyclesto clear references which just callsgc.collect(). If another thread has requested gc then that function is a no-op and doesn't break cycles. See python/cpython#137682ngoldbaum commentedon Nov 18, 2025
So it's a rare race, we should mark it as thread-unsafe. Thanks for taking a look.
ngoldbaum commentedon Dec 2, 2025
I looked a little closer at the
numpy.randomraces. I think we need to use critical sections in the Cython NumPy RNG implementation to make sure no new races are possible on the free-threaded build.Basically, whenever the internal state of the RNG is read from or mutated, we need to take a critical section. Here's how I modified
RandomState.set_state:Moving the
st.getcalls outside of the critical section avoids calling back into the CPython C API while the critical section is held.Now I need to audit the rest of the Cython code to find all the spots the internal state is read from or modified...
kumaraditya303 commentedon Dec 11, 2025
I ran the full test suite under TSAN and all of the data races inside numpy are fixed now. There are three remaining data races which happens when running under TSAN:
partial_vectorcall_fallbackThis is already fixed in CPython upstream main branch but was fixed as a result of performance optimization so will not be backported to 3.14.
dec_addstatusThis is tracked upstream in python/cpython#141148 and I am looking into this.
__Pyx_AddTracebackThis is a cython bug and is tracked upstream in cython/cython#7402
ngoldbaum commentedon Dec 11, 2025
Awesome, given that and that there are upstream issues to track all the remaining races, let's close this.
It's still far too slow to add this to CI, so people will only do this as a one-off for now. Let's not mess with the TSan suppression file until someone needs this, on the theory that the upstream issues will eventually be fixed.