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,7 +39,6 @@ EmptyStruct SharedInstance;
3439} // namespace
3540
3641TEST_SUBMODULE (thread, m) {
37-
3842 py::class_<IntStruct>(m, " IntStruct" ).def (py::init ([](const int i) { return IntStruct (i); }));
3943
4044 // implicitly_convertible uses loader_life_support when an implicit
@@ -67,6 +71,39 @@ TEST_SUBMODULE(thread, m) {
6771 py::class_<EmptyStruct>(m, " EmptyStruct" )
6872 .def_readonly_static (" SharedInstance" , &SharedInstance);
6973
74+ #if defined(PYBIND11_HAS_BARRIER)
75+ // In the free-threaded build, during PyThreadState_Clear, removing the thread from the biased
76+ // reference counting table may call destructors. Make sure that it doesn't crash.
77+ m.def (" test_pythread_state_clear_destructor" , [](py::type cls) {
78+ py::handle obj;
79+
80+ std::barrier barrier{2 };
81+ std::thread thread1{[&]() {
82+ py::gil_scoped_acquire gil;
83+ obj = cls ().release ();
84+ barrier.arrive_and_wait ();
85+ }};
86+ std::thread thread2{[&]() {
87+ py::gil_scoped_acquire gil;
88+ barrier.arrive_and_wait ();
89+ // ob_ref_shared becomes negative; transition to the queued state
90+ obj.dec_ref ();
91+ }};
92+
93+ // jthread is not supported by Apple Clang
94+ thread1.join ();
95+ thread2.join ();
96+ });
97+ #endif
98+
99+ m.attr (" has_barrier" ) =
100+ #ifdef PYBIND11_HAS_BARRIER
101+ true ;
102+ #else
103+ false ;
104+ #endif
105+ m.def (" acquire_gil" , []() { py::gil_scoped_acquire gil_acquired; });
106+
70107 // NOTE: std::string_view also uses loader_life_support to ensure that
71108 // the string contents remain alive, but that's a C++ 17 feature.
72109}
0 commit comments