Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@kumaraditya303
Copy link

@kumaraditya303 kumaraditya303 commented Nov 16, 2025

Relates #30085

This PR fixes data race in PyArray_DescrHash on descr->hash. The non-atomic load and store of hash can data race so it is now changed to use atomics. Relaxed ordering would be sufficient here but I don't think it is worth it so I have reused npy_atomic_{load/store}_ptr as the size of hash is same as pointer and uses seq-cst ordering.

@jorenham jorenham added the 39 - free-threading PRs and issues related to support for free-threading CPython (a.k.a. no-GIL, PEP 703) label Nov 16, 2025
@rgommers rgommers requested a review from ngoldbaum November 16, 2025 11:51
@ngoldbaum
Copy link
Member

Can you add a multithreaded test that exercises this code?

It's not clear to me how you'd hit a race in this code path or why this is coming up now. I want to make sure there aren't other issues like this that need to be fixed.

@kumaraditya303
Copy link
Author

Can you add a multithreaded test that exercises this code?

You can reproduce the data race by running test_return from numpy/tests/test_ctypeslib.py under pytest-run-parallel.

==================
WARNING: ThreadSanitizer: data race (pid=79191)
  Write of size 8 at 0x0001118e8390 by thread T8:
    #0 PyArray_DescrHash hashdescr.c:314 (_multiarray_umath.cpython-314t-darwin.so:arm64+0x1f636c)
    #1 PyObject_Hash <null> (libpython3.14t.dylib:arm64+0x1365a8)
    #2 tuple_hash <null> (libpython3.14t.dylib:arm64+0x17ddfc)
    #3 PyObject_Hash <null> (libpython3.14t.dylib:arm64+0x1365a8)
    #4 PyDict_GetItemRef <null> (libpython3.14t.dylib:arm64+0x104b3c)
    #5 _PyEval_EvalFrameDefault <null> (libpython3.14t.dylib:arm64+0x27a864)
    #6 _PyEval_Vector <null> (libpython3.14t.dylib:arm64+0x278c90)
    #7 _PyFunction_Vectorcall <null> (libpython3.14t.dylib:arm64+0x8b1cc)
    #8 method_vectorcall <null> (libpython3.14t.dylib:arm64+0x8f598)
    #9 _PyObject_Call <null> (libpython3.14t.dylib:arm64+0x8ad8c)
    #10 PyObject_Call <null> (libpython3.14t.dylib:arm64+0x8aeb4)
    #11 _PyEval_EvalFrameDefault <null> (libpython3.14t.dylib:arm64+0x27fbc0)
    #12 _PyEval_Vector <null> (libpython3.14t.dylib:arm64+0x278c90)
    #13 _PyFunction_Vectorcall <null> (libpython3.14t.dylib:arm64+0x8b1cc)
    #14 method_vectorcall <null> (libpython3.14t.dylib:arm64+0x8f638)
    #15 context_run <null> (libpython3.14t.dylib:arm64+0x2c3eec)
    #16 _PyEval_EvalFrameDefault <null> (libpython3.14t.dylib:arm64+0x283168)
    #17 _PyEval_Vector <null> (libpython3.14t.dylib:arm64+0x278c90)
    #18 _PyFunction_Vectorcall <null> (libpython3.14t.dylib:arm64+0x8b1cc)
    #19 method_vectorcall <null> (libpython3.14t.dylib:arm64+0x8f638)
    #20 _PyObject_Call <null> (libpython3.14t.dylib:arm64+0x8ae40)
    #21 PyObject_Call <null> (libpython3.14t.dylib:arm64+0x8aeb4)
    #22 thread_run <null> (libpython3.14t.dylib:arm64+0x416b08)
    #23 pythread_wrapper <null> (libpython3.14t.dylib:arm64+0x368cac)

  Previous read of size 8 at 0x0001118e8390 by thread T5:
    #0 PyArray_DescrHash hashdescr.c:313 (_multiarray_umath.cpython-314t-darwin.so:arm64+0x1f620c)
    #1 PyObject_Hash <null> (libpython3.14t.dylib:arm64+0x1365a8)
    #2 tuple_hash <null> (libpython3.14t.dylib:arm64+0x17ddfc)
    #3 PyObject_Hash <null> (libpython3.14t.dylib:arm64+0x1365a8)
    #4 PyDict_GetItemRef <null> (libpython3.14t.dylib:arm64+0x104b3c)
    #5 _PyEval_EvalFrameDefault <null> (libpython3.14t.dylib:arm64+0x27a864)
    #6 _PyEval_Vector <null> (libpython3.14t.dylib:arm64+0x278c90)
    #7 _PyFunction_Vectorcall <null> (libpython3.14t.dylib:arm64+0x8b1cc)
    #8 method_vectorcall <null> (libpython3.14t.dylib:arm64+0x8f598)
    #9 _PyObject_Call <null> (libpython3.14t.dylib:arm64+0x8ad8c)
    #10 PyObject_Call <null> (libpython3.14t.dylib:arm64+0x8aeb4)
    #11 _PyEval_EvalFrameDefault <null> (libpython3.14t.dylib:arm64+0x27fbc0)
    #12 _PyEval_Vector <null> (libpython3.14t.dylib:arm64+0x278c90)
    #13 _PyFunction_Vectorcall <null> (libpython3.14t.dylib:arm64+0x8b1cc)
    #14 method_vectorcall <null> (libpython3.14t.dylib:arm64+0x8f638)
    #15 context_run <null> (libpython3.14t.dylib:arm64+0x2c3eec)
    #16 _PyEval_EvalFrameDefault <null> (libpython3.14t.dylib:arm64+0x283168)
    #17 _PyEval_Vector <null> (libpython3.14t.dylib:arm64+0x278c90)
    #18 _PyFunction_Vectorcall <null> (libpython3.14t.dylib:arm64+0x8b1cc)
    #19 method_vectorcall <null> (libpython3.14t.dylib:arm64+0x8f638)
    #20 _PyObject_Call <null> (libpython3.14t.dylib:arm64+0x8ae40)
    #21 PyObject_Call <null> (libpython3.14t.dylib:arm64+0x8aeb4)
    #22 thread_run <null> (libpython3.14t.dylib:arm64+0x416b08)
    #23 pythread_wrapper <null> (libpython3.14t.dylib:arm64+0x368cac)

SUMMARY: ThreadSanitizer: data race hashdescr.c:314 in PyArray_DescrHash
==================

It's not clear to me how you'd hit a race in this code path or why this is coming up now. I want to make sure there aren't other issues like this that need to be fixed.

I didn't find any other place where numpy caches the hash of object so I think this is the only place which needs to be fixed.

@kumaraditya303
Copy link
Author

As a data point, CPython caches hashes for many objects and I had fixed data race in CPython similar to how it is done here. See python/cpython#139775 which fixed similar data races in datetime.

@ngoldbaum
Copy link
Member

Thanks for the extra context. I'll give this a once-over on Monday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

00 - Bug 39 - free-threading PRs and issues related to support for free-threading CPython (a.k.a. no-GIL, PEP 703)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants