Skip to content

Conversation

@SwayamInSync
Copy link
Member

closes #216

I took the reference for implementing as_integer_ratio from CPython's implementation

}
}

// this is thread-unsafe
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ngoldbaum precisely, Sleef_snprintf here is thread-unsafe, I believe just GIL won't help here as this is C routine and GIL only protects the Python objects.
Not 100% sure so need your opinion that whether GIL would be enough or we can lock this region with pthread_mutex

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that means that it's not safe to concurrently call Sleef_snprintf simultaneously in two threads (e.g. it's not re-entrant)?

In that case, yeah, I think you need a global lock.

I'd avoid using pthreads directly because then you'd need to do something else on Windows. Instead, I'd use PyMutex on Python 3.13 and newer and PyThread_type_lock on 3.12 and older. See e.g. the use of lapack_lite_lock in numpy/linalg/umath_linalg.cpp in NumPy, which solves a similar problem with the non-reentrant lapack_lite library.

You could also switch to C++ and use a C++ standard library but then you need to be careful you don't deadlock with the GIL by making sure you release it before doing any possibly blocking calls. The built-in lock types have deadlock protection against the GIL so you don't need to go through that trouble.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it'd be nice to add a multithreaded test for this as well. You can look at test_multithreading.py in NumPy for some patterns to use for multithreaded tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a multithreaded test for now only for testing this. Will push the lock after the tests get done

@SwayamInSync
Copy link
Member Author

It seems not to be crashing somehow @ngoldbaum can you check the test_multithreading.py here. If looks good then I can drop the lock commit and we can merge it

@ngoldbaum
Copy link
Member

Did you try running under TSan? You'll need to build python, numpy, SLEEF, and numpy-quaddtype all with the same compiler/sanitizer stack. See https://py-free-threading.github.io/thread_sanitizer/

@SwayamInSync
Copy link
Member Author

Do we need to add this in CI?

@ngoldbaum
Copy link
Member

I don't think so, a one-off local test every now and then is fine. It may make sense to add TSan testing along with supporting the free-threaded build and more extensive multithreaded testing, but not until a lot of work has happened towards that.

@SwayamInSync
Copy link
Member Author

cool, so I can push the lock adding commit

@SwayamInSync
Copy link
Member Author

SwayamInSync commented Nov 1, 2025

Did you try running under TSan? You'll need to build python, numpy, SLEEF, and numpy-quaddtype all with the same compiler/sanitizer stack. See py-free-threading.github.io/thread_sanitizer

Did this on macos and running Pytest didn't give any TSan warnings for test_multithreading.py, test_quaddtype.py there were 3 regarding BLAS ops which I'll handle separately in qblas

@SwayamInSync
Copy link
Member Author

SwayamInSync commented Nov 1, 2025

I'll push the code with the commented options for building with TSan so that in near future, anyone wants just uncomment and test

@SwayamInSync
Copy link
Member Author

@jorenham this stub test failure can be fix in your #218 ?

@jorenham
Copy link
Member

jorenham commented Nov 1, 2025

@jorenham this stub test failure can be fix in your #218 ?

Yes; stubtest does what it's supposed to do now 🎉

The errors can easily be fixed by copying these method stubs over from numpy:
https://github.com/numpy/numpy/blob/779929c42dd5dfa32f07f1311233abea9f026310/numpy/__init__.pyi#L4976-L4977

@SwayamInSync
Copy link
Member Author

@ngoldbaum is this ready to merge?

@SwayamInSync
Copy link
Member Author

@jorenham can you please take a look at this error in validating static types?

@jorenham
Copy link
Member

jorenham commented Nov 1, 2025

@jorenham can you please take a look at this error in validating static types?

The added methods need to be decorated with @override, imported from typing_extensions

@@ -1,5 +1,5 @@
from typing import Any, Literal, TypeAlias, final, overload

import builtins
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the explicit import? We use bool without the builtins.bool elsewhere in the stubs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied from the NumPy's stubs, I think both are just same.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in numpy it's needed because bool is shadowed by np.bool. But that shouldn't be problem here, so no need for the builtins._

@ngoldbaum
Copy link
Member

I try to avoid looking at code on weekends - I'll look at this next week.

Copy link
Member

@ngoldbaum ngoldbaum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for taking a little while to look at this. I wanted to have time to run TSan testing locally.

For what it's worth, on the free-threaded build, I see a data race inside Sleef_iunordq1 on a global value in the new multithreaded test you added. Possibly this is shibatch/sleef#560. We should probably report it upstream as a bug.

# export CFLAGS="-fsanitize=thread -g -O0"
# export CXXFLAGS="-fsanitize=thread -g -O0"
# export LDFLAGS="-fsanitize=thread"
# python -m pip install . -vv --no-build-isolation -Csetup-args=-Db_sanitize=thread 2>&1 | tee build_log.txt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you also need to pass --no-binary :all: as well, otherwise you'll get a numpy wheel that doesn't have TSan instrumentation. I'd also suggest using a TSan build of CPython and link to https://py-free-threading.github.io/thread_sanitizer/#compiling-cpython-and-foundational-packages-with-thread-sanitizer.

A somewhat more verbose but perhaps clearer way to do this is to explicitly install all the build deps, then build quaddtype with --no-build-isolation:

# either on the cpython_sanity docker image or 
# after building and installing a TSan Python build
python -m pip install meson meson-python wheel ninja
python -m pip install "numpy @ git+https://github.com/numpy/numpy" -C'setup-args=-Db_sanitize=thread'
python -m pip install . --no-build-isolation -C'setup-args=-Db_sanitize=thread'

No need to pass any special arguments for pure-python dependencies.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After I figured out how to compile SLEEF with TSan instrumentation, I got this race report in the new test you added:

quaddtype/tests/test_multithreading.py::test_as_integer_ratio_reconstruction ==================
WARNING: ThreadSanitizer: data race (pid=13598)
  Read of size 8 at 0x00015ef30f18 by thread T14:
    #0 Sleef_iunordq1 <null> (_quaddtype_main.cpython-314t-darwin.so:arm64+0xb8ffc)
    #1 QuadPrecision_as_integer_ratio <null> (_quaddtype_main.cpython-314t-darwin.so:arm64+0xdb6c)
    #2 method_vectorcall_NOARGS descrobject.c:448 (libpython3.14t.dylib:arm64+0xa2218)
    #3 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8ae78)
    #4 _PyEval_EvalFrameDefault generated_cases.c.h:1619 (libpython3.14t.dylib:arm64+0x27e560)
    #5 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #6 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #7 method_vectorcall classobject.c:73 (libpython3.14t.dylib:arm64+0x8f924)
    #8 context_run context.c:728 (libpython3.14t.dylib:arm64+0x2c5404)
    #9 method_vectorcall_FASTCALL_KEYWORDS descrobject.c:421 (libpython3.14t.dylib:arm64+0xa2124)
    #10 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8ae78)
    #11 _PyEval_EvalFrameDefault generated_cases.c.h:1619 (libpython3.14t.dylib:arm64+0x27e560)
    #12 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #13 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #14 method_vectorcall classobject.c:73 (libpython3.14t.dylib:arm64+0x8f924)
    #15 _PyObject_Call call.c:348 (libpython3.14t.dylib:arm64+0x8b12c)
    #16 PyObject_Call call.c:373 (libpython3.14t.dylib:arm64+0x8b1a0)
    #17 thread_run _threadmodule.c:359 (libpython3.14t.dylib:arm64+0x41ae78)
    #18 pythread_wrapper thread_pthread.h:242 (libpython3.14t.dylib:arm64+0x36aeb0)

  Previous write of size 8 at 0x00015ef30f18 by thread T74:
    #0 disp_iunordq1 <null> (_quaddtype_main.cpython-314t-darwin.so:arm64+0xbada8)
    #1 Sleef_iunordq1 <null> (_quaddtype_main.cpython-314t-darwin.so:arm64+0xb9014)
    #2 QuadPrecision_as_integer_ratio <null> (_quaddtype_main.cpython-314t-darwin.so:arm64+0xdb6c)
    #3 method_vectorcall_NOARGS descrobject.c:448 (libpython3.14t.dylib:arm64+0xa2218)
    #4 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8ae78)
    #5 _PyEval_EvalFrameDefault generated_cases.c.h:1619 (libpython3.14t.dylib:arm64+0x27e560)
    #6 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #7 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #8 method_vectorcall classobject.c:73 (libpython3.14t.dylib:arm64+0x8f924)
    #9 context_run context.c:728 (libpython3.14t.dylib:arm64+0x2c5404)
    #10 method_vectorcall_FASTCALL_KEYWORDS descrobject.c:421 (libpython3.14t.dylib:arm64+0xa2124)
    #11 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8ae78)
    #12 _PyEval_EvalFrameDefault generated_cases.c.h:1619 (libpython3.14t.dylib:arm64+0x27e560)
    #13 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #14 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #15 method_vectorcall classobject.c:73 (libpython3.14t.dylib:arm64+0x8f924)
    #16 _PyObject_Call call.c:348 (libpython3.14t.dylib:arm64+0x8b12c)
    #17 PyObject_Call call.c:373 (libpython3.14t.dylib:arm64+0x8b1a0)
    #18 thread_run _threadmodule.c:359 (libpython3.14t.dylib:arm64+0x41ae78)
    #19 pythread_wrapper thread_pthread.h:242 (libpython3.14t.dylib:arm64+0x36aeb0)

  Location is global 'pnt_iunordq1' at 0x00015ef30f18 (_quaddtype_main.cpython-314t-darwin.so+0x104f18)

  Thread T14 (tid=223552319, running) created by main thread at:
    #0 pthread_create <null> (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x2f708)
    #1 do_start_joinable_thread thread_pthread.h:289 (libpython3.14t.dylib:arm64+0x369b70)
    #2 PyThread_start_joinable_thread thread_pthread.h:331 (libpython3.14t.dylib:arm64+0x3699b8)
    #3 do_start_new_thread _threadmodule.c:1868 (libpython3.14t.dylib:arm64+0x41aa2c)
    #4 thread_PyThread_start_joinable_thread _threadmodule.c:1991 (libpython3.14t.dylib:arm64+0x4197b8)
    #5 cfunction_call methodobject.c:564 (libpython3.14t.dylib:arm64+0x12d2e4)
    #6 _PyObject_MakeTpCall call.c:242 (libpython3.14t.dylib:arm64+0x8a324)
    #7 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8af14)
    #8 _PyEval_EvalFrameDefault generated_cases.c.h:3227 (libpython3.14t.dylib:arm64+0x28309c)
    #9 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #10 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #11 method_vectorcall classobject.c:95 (libpython3.14t.dylib:arm64+0x8f9c0)
    #12 _PyObject_Call call.c:348 (libpython3.14t.dylib:arm64+0x8b12c)
    #13 PyObject_Call call.c:373 (libpython3.14t.dylib:arm64+0x8b1a0)
    #14 _PyEval_EvalFrameDefault generated_cases.c.h:2654 (libpython3.14t.dylib:arm64+0x28128c)
    #15 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #16 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #17 _PyObject_VectorcallDictTstate call.c:146 (libpython3.14t.dylib:arm64+0x8a084)
    #18 _PyObject_Call_Prepend call.c:504 (libpython3.14t.dylib:arm64+0x8bac8)
    #19 call_method typeobject.c:2937 (libpython3.14t.dylib:arm64+0x197e5c)
    #20 slot_tp_call typeobject.c:10232 (libpython3.14t.dylib:arm64+0x197c7c)
    #21 _PyObject_MakeTpCall call.c:242 (libpython3.14t.dylib:arm64+0x8a324)
    #22 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8af14)
    #23 _PyEval_EvalFrameDefault generated_cases.c.h:3227 (libpython3.14t.dylib:arm64+0x28309c)
    #24 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #25 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #26 _PyObject_VectorcallDictTstate call.c:146 (libpython3.14t.dylib:arm64+0x8a084)
    #27 _PyObject_Call_Prepend call.c:504 (libpython3.14t.dylib:arm64+0x8bac8)
    #28 call_method typeobject.c:2937 (libpython3.14t.dylib:arm64+0x197e5c)
    #29 slot_tp_call typeobject.c:10232 (libpython3.14t.dylib:arm64+0x197c7c)
    #30 _PyObject_Call call.c:361 (libpython3.14t.dylib:arm64+0x8b0ec)
    #31 PyObject_Call call.c:373 (libpython3.14t.dylib:arm64+0x8b1a0)
    #32 _PyEval_EvalFrameDefault generated_cases.c.h:2654 (libpython3.14t.dylib:arm64+0x28128c)
    #33 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #34 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #35 _PyObject_VectorcallDictTstate call.c:146 (libpython3.14t.dylib:arm64+0x8a084)
    #36 _PyObject_Call_Prepend call.c:504 (libpython3.14t.dylib:arm64+0x8bac8)
    #37 call_method typeobject.c:2937 (libpython3.14t.dylib:arm64+0x197e5c)
    #38 slot_tp_call typeobject.c:10232 (libpython3.14t.dylib:arm64+0x197c7c)
    #39 _PyObject_MakeTpCall call.c:242 (libpython3.14t.dylib:arm64+0x8a324)
    #40 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8af14)
    #41 _PyEval_EvalFrameDefault generated_cases.c.h:3227 (libpython3.14t.dylib:arm64+0x28309c)
    #42 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #43 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #44 _PyObject_VectorcallDictTstate call.c:146 (libpython3.14t.dylib:arm64+0x8a084)
    #45 _PyObject_Call_Prepend call.c:504 (libpython3.14t.dylib:arm64+0x8bac8)
    #46 call_method typeobject.c:2937 (libpython3.14t.dylib:arm64+0x197e5c)
    #47 slot_tp_call typeobject.c:10232 (libpython3.14t.dylib:arm64+0x197c7c)
    #48 _PyObject_MakeTpCall call.c:242 (libpython3.14t.dylib:arm64+0x8a324)
    #49 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8af14)
    #50 _PyEval_EvalFrameDefault generated_cases.c.h:2959 (libpython3.14t.dylib:arm64+0x282078)
    #51 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #52 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #53 _PyObject_VectorcallDictTstate call.c:146 (libpython3.14t.dylib:arm64+0x8a084)
    #54 _PyObject_Call_Prepend call.c:504 (libpython3.14t.dylib:arm64+0x8bac8)
    #55 call_method typeobject.c:2937 (libpython3.14t.dylib:arm64+0x197e5c)
    #56 slot_tp_call typeobject.c:10232 (libpython3.14t.dylib:arm64+0x197c7c)
    #57 _PyObject_MakeTpCall call.c:242 (libpython3.14t.dylib:arm64+0x8a324)
    #58 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8af14)
    #59 _PyEval_EvalFrameDefault generated_cases.c.h:2959 (libpython3.14t.dylib:arm64+0x282078)
    #60 PyEval_EvalCode ceval.c:857 (libpython3.14t.dylib:arm64+0x279ed8)
    #61 run_mod pythonrun.c:1459 (libpython3.14t.dylib:arm64+0x348844)
    #62 _PyRun_SimpleFileObject pythonrun.c:521 (libpython3.14t.dylib:arm64+0x343f30)
    #63 _PyRun_AnyFileObject pythonrun.c:81 (libpython3.14t.dylib:arm64+0x343684)
    #64 pymain_run_file main.c:429 (libpython3.14t.dylib:arm64+0x384ea4)
    #65 Py_RunMain main.c:775 (libpython3.14t.dylib:arm64+0x3842d0)
    #66 pymain_main main.c:805 (libpython3.14t.dylib:arm64+0x38473c)
    #67 Py_BytesMain main.c:829 (libpython3.14t.dylib:arm64+0x384810)
    #68 main <null> (python3.14:arm64+0x10000073c)

  Thread T74 (tid=223552380, running) created by main thread at:
    #0 pthread_create <null> (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x2f708)
    #1 do_start_joinable_thread thread_pthread.h:289 (libpython3.14t.dylib:arm64+0x369b70)
    #2 PyThread_start_joinable_thread thread_pthread.h:331 (libpython3.14t.dylib:arm64+0x3699b8)
    #3 do_start_new_thread _threadmodule.c:1868 (libpython3.14t.dylib:arm64+0x41aa2c)
    #4 thread_PyThread_start_joinable_thread _threadmodule.c:1991 (libpython3.14t.dylib:arm64+0x4197b8)
    #5 cfunction_call methodobject.c:564 (libpython3.14t.dylib:arm64+0x12d2e4)
    #6 _PyObject_MakeTpCall call.c:242 (libpython3.14t.dylib:arm64+0x8a324)
    #7 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8af14)
    #8 _PyEval_EvalFrameDefault generated_cases.c.h:3227 (libpython3.14t.dylib:arm64+0x28309c)
    #9 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #10 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #11 method_vectorcall classobject.c:95 (libpython3.14t.dylib:arm64+0x8f9c0)
    #12 _PyObject_Call call.c:348 (libpython3.14t.dylib:arm64+0x8b12c)
    #13 PyObject_Call call.c:373 (libpython3.14t.dylib:arm64+0x8b1a0)
    #14 _PyEval_EvalFrameDefault generated_cases.c.h:2654 (libpython3.14t.dylib:arm64+0x28128c)
    #15 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #16 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #17 _PyObject_VectorcallDictTstate call.c:146 (libpython3.14t.dylib:arm64+0x8a084)
    #18 _PyObject_Call_Prepend call.c:504 (libpython3.14t.dylib:arm64+0x8bac8)
    #19 call_method typeobject.c:2937 (libpython3.14t.dylib:arm64+0x197e5c)
    #20 slot_tp_call typeobject.c:10232 (libpython3.14t.dylib:arm64+0x197c7c)
    #21 _PyObject_MakeTpCall call.c:242 (libpython3.14t.dylib:arm64+0x8a324)
    #22 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8af14)
    #23 _PyEval_EvalFrameDefault generated_cases.c.h:3227 (libpython3.14t.dylib:arm64+0x28309c)
    #24 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #25 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #26 _PyObject_VectorcallDictTstate call.c:146 (libpython3.14t.dylib:arm64+0x8a084)
    #27 _PyObject_Call_Prepend call.c:504 (libpython3.14t.dylib:arm64+0x8bac8)
    #28 call_method typeobject.c:2937 (libpython3.14t.dylib:arm64+0x197e5c)
    #29 slot_tp_call typeobject.c:10232 (libpython3.14t.dylib:arm64+0x197c7c)
    #30 _PyObject_Call call.c:361 (libpython3.14t.dylib:arm64+0x8b0ec)
    #31 PyObject_Call call.c:373 (libpython3.14t.dylib:arm64+0x8b1a0)
    #32 _PyEval_EvalFrameDefault generated_cases.c.h:2654 (libpython3.14t.dylib:arm64+0x28128c)
    #33 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #34 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #35 _PyObject_VectorcallDictTstate call.c:146 (libpython3.14t.dylib:arm64+0x8a084)
    #36 _PyObject_Call_Prepend call.c:504 (libpython3.14t.dylib:arm64+0x8bac8)
    #37 call_method typeobject.c:2937 (libpython3.14t.dylib:arm64+0x197e5c)
    #38 slot_tp_call typeobject.c:10232 (libpython3.14t.dylib:arm64+0x197c7c)
    #39 _PyObject_MakeTpCall call.c:242 (libpython3.14t.dylib:arm64+0x8a324)
    #40 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8af14)
    #41 _PyEval_EvalFrameDefault generated_cases.c.h:3227 (libpython3.14t.dylib:arm64+0x28309c)
    #42 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #43 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #44 _PyObject_VectorcallDictTstate call.c:146 (libpython3.14t.dylib:arm64+0x8a084)
    #45 _PyObject_Call_Prepend call.c:504 (libpython3.14t.dylib:arm64+0x8bac8)
    #46 call_method typeobject.c:2937 (libpython3.14t.dylib:arm64+0x197e5c)
    #47 slot_tp_call typeobject.c:10232 (libpython3.14t.dylib:arm64+0x197c7c)
    #48 _PyObject_MakeTpCall call.c:242 (libpython3.14t.dylib:arm64+0x8a324)
    #49 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8af14)
    #50 _PyEval_EvalFrameDefault generated_cases.c.h:2959 (libpython3.14t.dylib:arm64+0x282078)
    #51 _PyEval_Vector ceval.c:1965 (libpython3.14t.dylib:arm64+0x27a2fc)
    #52 _PyFunction_Vectorcall call.c (libpython3.14t.dylib:arm64+0x8b4b8)
    #53 _PyObject_VectorcallDictTstate call.c:146 (libpython3.14t.dylib:arm64+0x8a084)
    #54 _PyObject_Call_Prepend call.c:504 (libpython3.14t.dylib:arm64+0x8bac8)
    #55 call_method typeobject.c:2937 (libpython3.14t.dylib:arm64+0x197e5c)
    #56 slot_tp_call typeobject.c:10232 (libpython3.14t.dylib:arm64+0x197c7c)
    #57 _PyObject_MakeTpCall call.c:242 (libpython3.14t.dylib:arm64+0x8a324)
    #58 PyObject_Vectorcall call.c:327 (libpython3.14t.dylib:arm64+0x8af14)
    #59 _PyEval_EvalFrameDefault generated_cases.c.h:2959 (libpython3.14t.dylib:arm64+0x282078)
    #60 PyEval_EvalCode ceval.c:857 (libpython3.14t.dylib:arm64+0x279ed8)
    #61 run_mod pythonrun.c:1459 (libpython3.14t.dylib:arm64+0x348844)
    #62 _PyRun_SimpleFileObject pythonrun.c:521 (libpython3.14t.dylib:arm64+0x343f30)
    #63 _PyRun_AnyFileObject pythonrun.c:81 (libpython3.14t.dylib:arm64+0x343684)
    #64 pymain_run_file main.c:429 (libpython3.14t.dylib:arm64+0x384ea4)
    #65 Py_RunMain main.c:775 (libpython3.14t.dylib:arm64+0x3842d0)
    #66 pymain_main main.c:805 (libpython3.14t.dylib:arm64+0x38473c)
    #67 Py_BytesMain main.c:829 (libpython3.14t.dylib:arm64+0x384810)
    #68 main <null> (python3.14:arm64+0x10000073c)

SUMMARY: ThreadSanitizer: data race (_quaddtype_main.cpython-314t-darwin.so:arm64+0xb8ffc) in Sleef_iunordq1+0x38
==================

I think the locking that's going to be needed is probably not limited to snprintf. In this case it looks like there's global state in the iunordq1 implementation.

PyObject *numerator = quad_to_pylong(mantissa);
if(numerator == NULL)
{
Py_DECREF(numerator);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it is annoying, testing error cases catches stuff like this...

Suggested change
Py_DECREF(numerator);
Py_DECREF(py_exp);

}
PyObject *denominator = PyLong_FromLong(1);
if (denominator == NULL) {
Py_DECREF(numerator);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a goto error for this since the cleanup is repeated below.

Suggested change
Py_DECREF(numerator);
Py_DECREF(py_exp);
Py_DECREF(numerator);

# '-DCMAKE_C_FLAGS=-fsanitize=thread -g',
# '-DCMAKE_CXX_FLAGS=-fsanitize=thread -g',
# '-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=thread',
# '-DCMAKE_SHARED_LINKER_FLAGS=-fsanitize=thread',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I poked a little at this. It looks like this doesn't get passed down into the subproject by default so this manual patching is necessary. Too bad...

Also maybe worth mentioning in the comment above that you also need to delete the sleef subproject completely for this change to have any impact unless you're starting from a fresh clone of the repo. At least that's what I had to do for this change to have any effect.

@SwayamInSync
Copy link
Member Author

That's great capture, I was using the SLEEF from the subproject itself, thanks a lot @ngoldbaum
I will resolve them and will add a section in README on building with TSan.

Regarding SLEEF (I didn't know pytorch uses it) but in QBLAS I am also using vectorized functions. I might need to evaluate this part extensively as from the related issues and this one itself.
Locking everytime can hardly damage the performance

We are on SLEEF v3.8 (LTS is 3.9, but focussed on DFT not Quad) in short optimal fixes might can take time, but the current ones are resolvable

@SwayamInSync
Copy link
Member Author

@ngoldbaum
Reading the TSan warning, it seems the issue is lazy CPU feature detection. The Sleef_iunordq1 is a wrapper that calls the best available function as per CPU whose pointer is stored in pnt_iunordq1 via some dispatching mechanism that triggers inside disp_iunordq1 and in order to keep it cached this optimal function pointer (pnt_iunordq1) is given global state.

So I think the workflow would be: both the T14 and T74 threads triggered Sleef_iunordq1 **for the first time simultaneously before the function pointer was properly initialized, which leads both dispatchers to race while trying to update this pointer.

I will report this to SLEEF, and I think till then in ours we have 2 options

  • lock (this will be the ultimate gurantee)
  • init all the ops during module initialization (this should let all the init of all global pointers) something like
PyMODINIT_FUNC
PyInit_quaddtype(void)
{
    PyObject *m;
    
    // ... other initialization ...
    
    // SLEEF init
    Sleef_quad dummy = sleef_q(1LL, 0ULL, 0);
    Sleef_quad dummy2 = sleef_q(2LL, 0ULL, 0);
    int dummy_int;
    
    // Warmup all SLEEF functions you use
    Sleef_iunordq1(dummy, dummy2);
    Sleef_icmpgeq1(dummy, dummy2);
    Sleef_fabsq1(dummy);
    Sleef_frexpq1(dummy, &dummy_int);
    Sleef_cast_from_doubleq1(1.0);    
    return m;
}

This might not gurantee the other races if function implementation itself had issues

@SwayamInSync
Copy link
Member Author

Interesting, I am somehow still now able to see the race condition on x86-64 machine

~/temp/OSS/numpy-user-dtypes$ TSAN_OPTIONS="verbosity=1" pytest quaddtype/tests/test_multithreading.py -s 2>&1 | head -20
==4190773==Installed the sigaction for signal 11
==4190773==Installed the sigaction for signal 7
==4190773==Installed the sigaction for signal 8
***** Running under ThreadSanitizer v3 (pid 4190773) *****
============================= test session starts ==============================
platform linux -- Python 3.14.0+, pytest-8.4.2, pluggy-1.6.0
rootdir: /home/ssingh/temp/OSS/numpy-user-dtypes/quaddtype
configfile: pyproject.toml
collected 1 item

quaddtype/tests/test_multithreading.py .

============================== 1 passed in 11.00s ==============================
Stats: SizeClassAllocator64: 20M mapped (7M rss) in 1967503 allocations; remains 3666
  01 (    16): mapped:    256K allocs:    6784 frees:    6400 inuse:    384 num_freed_chunks   16000 avail:  16384 rss:      8K releases:      2 last released:    248K region: 0x720400000000
  02 (    32): mapped:    768K allocs:  830208 frees:  829952 inuse:    256 num_freed_chunks   24320 avail:  24576 rss:    592K releases:     10 last released:    180K region: 0x720800000000
  03 (    48): mapped:    256K allocs:    6528 frees:    6400 inuse:    128 num_freed_chunks    5333 avail:   5461 rss:     12K releases:      2 last released:    240K region: 0x720c00000000
  04 (    64): mapped:    256K allocs:     256 frees:       0 inuse:    256 num_freed_chunks    3840 avail:   4096 rss:     16K releases:      0 last released:      0K region: 0x721000000000
  05 (    80): mapped:    256K allocs:     128 frees:       0 inuse:    128 num_freed_chunks    3148 avail:   3276 rss:      8K releases:      0 last released:      0K region: 0x721400000000
  06 (    96): mapped:    256K allocs:     128 frees:       0 inuse:    128 num_freed_chunks    2602 avail:   2730 rss:      4K releases:      0 last released:      0K region: 0x721800000000

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QuadPrecision.is_integer() and QuadPrecision.as_integer_ratio()

4 participants