1515#include < chrono>
1616#include < thread>
1717
18+ #if defined(PYBIND11_HAS_STD_BARRIER)
19+ # include < barrier>
20+ #endif
21+
1822namespace py = pybind11;
1923
2024namespace {
@@ -34,7 +38,6 @@ EmptyStruct SharedInstance;
3438} // namespace
3539
3640TEST_SUBMODULE (thread, m) {
37-
3841 py::class_<IntStruct>(m, " IntStruct" ).def (py::init ([](const int i) { return IntStruct (i); }));
3942
4043 // implicitly_convertible uses loader_life_support when an implicit
@@ -67,6 +70,39 @@ TEST_SUBMODULE(thread, m) {
6770 py::class_<EmptyStruct>(m, " EmptyStruct" )
6871 .def_readonly_static (" SharedInstance" , &SharedInstance);
6972
73+ #if defined(PYBIND11_HAS_STD_BARRIER)
74+ // In the free-threaded build, during PyThreadState_Clear, removing the thread from the biased
75+ // reference counting table may call destructors. Make sure that it doesn't crash.
76+ m.def (" test_pythread_state_clear_destructor" , [](py::type cls) {
77+ py::handle obj;
78+
79+ std::barrier barrier{2 };
80+ std::thread thread1{[&]() {
81+ py::gil_scoped_acquire gil;
82+ obj = cls ().release ();
83+ barrier.arrive_and_wait ();
84+ }};
85+ std::thread thread2{[&]() {
86+ py::gil_scoped_acquire gil;
87+ barrier.arrive_and_wait ();
88+ // ob_ref_shared becomes negative; transition to the queued state
89+ obj.dec_ref ();
90+ }};
91+
92+ // jthread is not supported by Apple Clang
93+ thread1.join ();
94+ thread2.join ();
95+ });
96+ #endif
97+
98+ m.attr (" defined_PYBIND11_HAS_STD_BARRIER" ) =
99+ #ifdef PYBIND11_HAS_STD_BARRIER
100+ true ;
101+ #else
102+ false ;
103+ #endif
104+ m.def (" acquire_gil" , []() { py::gil_scoped_acquire gil_acquired; });
105+
70106 // NOTE: std::string_view also uses loader_life_support to ensure that
71107 // the string contents remain alive, but that's a C++ 17 feature.
72108}
0 commit comments