1515#include < chrono>
1616#include < thread>
1717
18+ #if defined(PYBIND11_CPP20) && defined(__has_include) && __has_include(<barrier>)
19+ # define PYBIND11_HAS_BARRIER 1
20+ # include < barrier>
21+ #endif
22+
1823namespace py = pybind11;
1924
2025namespace {
@@ -34,6 +39,9 @@ EmptyStruct SharedInstance;
3439} // namespace
3540
3641TEST_SUBMODULE (thread, m) {
42+ #ifdef Py_GIL_DISABLED
43+ PyUnstable_Module_SetGIL (m.ptr (), Py_MOD_GIL_NOT_USED);
44+ #endif
3745
3846 py::class_<IntStruct>(m, " IntStruct" ).def (py::init ([](const int i) { return IntStruct (i); }));
3947
@@ -67,6 +75,39 @@ TEST_SUBMODULE(thread, m) {
6775 py::class_<EmptyStruct>(m, " EmptyStruct" )
6876 .def_readonly_static (" SharedInstance" , &SharedInstance);
6977
78+ #if defined(PYBIND11_HAS_BARRIER)
79+ // In the free-threaded build, during PyThreadState_Clear, removing the thread from the biased
80+ // reference counting table may call destructors. Make sure that it doesn't crash.
81+ m.def (" test_pythread_state_clear_destructor" , [](py::type cls) {
82+ py::handle obj;
83+
84+ std::barrier barrier{2 };
85+ std::thread thread1{[&]() {
86+ py::gil_scoped_acquire gil;
87+ obj = cls ().release ();
88+ barrier.arrive_and_wait ();
89+ }};
90+ std::thread thread2{[&]() {
91+ py::gil_scoped_acquire gil;
92+ barrier.arrive_and_wait ();
93+ // ob_ref_shared becomes negative; transition to the queued state
94+ obj.dec_ref ();
95+ }};
96+
97+ // jthread is not supported by Apple Clang
98+ thread1.join ();
99+ thread2.join ();
100+ });
101+ #endif
102+
103+ m.attr (" has_barrier" ) =
104+ #ifdef PYBIND11_HAS_BARRIER
105+ true ;
106+ #else
107+ false ;
108+ #endif
109+ m.def (" acquire_gil" , []() { py::gil_scoped_acquire gil_acquired; });
110+
70111 // NOTE: std::string_view also uses loader_life_support to ensure that
71112 // the string contents remain alive, but that's a C++ 17 feature.
72113}
0 commit comments