@@ -204,6 +204,90 @@ test_critical_sections_threads(PyObject *self, PyObject *Py_UNUSED(args))
204204 Py_DECREF (test_data .obj1 );
205205 Py_RETURN_NONE ;
206206}
207+
208+ static void
209+ pysleep (int ms )
210+ {
211+ #ifdef MS_WINDOWS
212+ Sleep (ms );
213+ #else
214+ usleep (ms * 1000 );
215+ #endif
216+ }
217+
218+ struct test_data_gc {
219+ PyObject * obj ;
220+ Py_ssize_t num_threads ;
221+ Py_ssize_t id ;
222+ Py_ssize_t countdown ;
223+ PyEvent done_event ;
224+ PyEvent ready ;
225+ };
226+
227+ static void
228+ thread_gc (void * arg )
229+ {
230+ struct test_data_gc * test_data = arg ;
231+ PyGILState_STATE gil = PyGILState_Ensure ();
232+
233+ Py_ssize_t id = _Py_atomic_add_ssize (& test_data -> id , 1 );
234+ if (id == test_data -> num_threads - 1 ) {
235+ _PyEvent_Notify (& test_data -> ready );
236+ }
237+ else {
238+ // wait for all test threads to more reliably reproduce the issue.
239+ PyEvent_Wait (& test_data -> ready );
240+ }
241+
242+ if (id == 0 ) {
243+ Py_BEGIN_CRITICAL_SECTION (test_data -> obj );
244+ // pause long enough that the lock would be handed off directly to
245+ // a waiting thread.
246+ pysleep (5 );
247+ PyGC_Collect ();
248+ Py_END_CRITICAL_SECTION ();
249+ }
250+ else if (id == 1 ) {
251+ pysleep (1 );
252+ Py_BEGIN_CRITICAL_SECTION (test_data -> obj );
253+ pysleep (1 );
254+ Py_END_CRITICAL_SECTION ();
255+ }
256+ else if (id == 2 ) {
257+ // sleep long enough so that thread 0 is waiting to stop the world
258+ pysleep (6 );
259+ Py_BEGIN_CRITICAL_SECTION (test_data -> obj );
260+ pysleep (1 );
261+ Py_END_CRITICAL_SECTION ();
262+ }
263+
264+ PyGILState_Release (gil );
265+ if (_Py_atomic_add_ssize (& test_data -> countdown , -1 ) == 1 ) {
266+ // last thread to finish sets done_event
267+ _PyEvent_Notify (& test_data -> done_event );
268+ }
269+ }
270+
271+ static PyObject *
272+ test_critical_sections_gc (PyObject * self , PyObject * Py_UNUSED (args ))
273+ {
274+ // gh-118332: Contended critical sections should not deadlock with GC
275+ const Py_ssize_t NUM_THREADS = 3 ;
276+ struct test_data_gc test_data = {
277+ .obj = PyDict_New (),
278+ .countdown = NUM_THREADS ,
279+ .num_threads = NUM_THREADS ,
280+ };
281+ assert (test_data .obj != NULL );
282+
283+ for (int i = 0 ; i < NUM_THREADS ; i ++ ) {
284+ PyThread_start_new_thread (& thread_gc , & test_data );
285+ }
286+ PyEvent_Wait (& test_data .done_event );
287+ Py_DECREF (test_data .obj );
288+ Py_RETURN_NONE ;
289+ }
290+
207291#endif
208292
209293static PyMethodDef test_methods [] = {
@@ -212,6 +296,7 @@ static PyMethodDef test_methods[] = {
212296 {"test_critical_sections_suspend" , test_critical_sections_suspend , METH_NOARGS },
213297#ifdef Py_CAN_START_THREADS
214298 {"test_critical_sections_threads" , test_critical_sections_threads , METH_NOARGS },
299+ {"test_critical_sections_gc" , test_critical_sections_gc , METH_NOARGS },
215300#endif
216301 {NULL , NULL } /* sentinel */
217302};
0 commit comments