44
55#include "parts.h"
66#include "pycore_critical_section.h"
7+ #include "pycore_interp_structs.h" // _stoptheworld_state
8+ #include "pycore_lock.h" // _Py_HAS_PARKED
79#include "pycore_pystate.h"
810#include "pycore_pythread.h"
911
@@ -464,6 +466,209 @@ test_critical_sections_stw(PyObject *self, PyObject *Py_UNUSED(args))
464466
465467#endif // Py_CAN_START_THREADS
466468
469+ #if defined(Py_GIL_DISABLED ) && defined(Py_CAN_START_THREADS )
470+
471+ // gh-158195: Test that a thread waiting to stop the world does not try to
472+ // re-acquire its critical sections while it owns the stop-the-world mutex.
473+ //
474+ // The main thread holds the interpreter's stop-the-world mutex so that both
475+ // worker threads must wait for it:
476+ //
477+ // 1. The "waiter" thread holds a critical section on `obj` and requests
478+ // stop-the-world. It parks on the STW mutex and, because it detaches
479+ // while waiting, releases the mutex of `obj`.
480+ // 2. The "pinned" thread locks `obj->ob_mutex` directly, which mimics a
481+ // pinned critical section: the mutex is not released when the thread
482+ // detaches. It then requests stop-the-world and parks behind the waiter.
483+ // 3. The main thread releases the STW mutex. The waiter acquires it.
484+ //
485+ // Before the fix, the waiter re-acquired `obj` when it re-attached while
486+ // owning the STW mutex, and blocked forever: the pinned thread holds `obj`
487+ // and waits for the STW mutex. This test deadlocks on builds without the fix.
488+
489+ enum {
490+ STW_HANDOFF_ONE_MUTEX ,
491+ STW_HANDOFF_TWO_MUTEXES ,
492+ STW_HANDOFF_NESTED ,
493+ };
494+
495+ struct test_data_stw_handoff {
496+ PyObject * obj ;
497+ PyObject * other ;
498+ PyObject * inner ;
499+ int mode ;
500+ PyMutex * stw_mutex ;
501+ PyThreadState * pinned_tstate ;
502+ };
503+
504+ static void
505+ detached_sleep (int ms )
506+ {
507+ Py_BEGIN_ALLOW_THREADS
508+ pysleep (ms );
509+ Py_END_ALLOW_THREADS
510+ }
511+
512+ static void
513+ stw_handoff_stop_start (struct test_data_stw_handoff * test_data )
514+ {
515+ PyInterpreterState * interp = PyInterpreterState_Get ();
516+ _PyEval_StopTheWorld (interp );
517+ // A critical section begun and ended while the world is stopped must not
518+ // resume the critical sections suspended by the STW request.
519+ Py_BEGIN_CRITICAL_SECTION (test_data -> inner );
520+ Py_END_CRITICAL_SECTION ();
521+ _PyEval_StartTheWorld (interp );
522+
523+ // The critical section is restored by the time StartTheWorld returns.
524+ PyThreadState * tstate = PyThreadState_GET ();
525+ assert (_PyCriticalSection_IsActive (tstate -> critical_section ));
526+ assert (PyMutex_IsLocked (& test_data -> obj -> ob_mutex ));
527+ (void )tstate ;
528+ }
529+
530+ static void
531+ thread_stw_handoff_waiter (void * arg )
532+ {
533+ struct test_data_stw_handoff * test_data = arg ;
534+ PyGILState_STATE gil = PyGILState_Ensure ();
535+
536+ switch (test_data -> mode ) {
537+ case STW_HANDOFF_ONE_MUTEX :
538+ Py_BEGIN_CRITICAL_SECTION (test_data -> obj );
539+ stw_handoff_stop_start (test_data );
540+ Py_END_CRITICAL_SECTION ();
541+ break ;
542+ case STW_HANDOFF_TWO_MUTEXES :
543+ Py_BEGIN_CRITICAL_SECTION2 (test_data -> obj , test_data -> other );
544+ stw_handoff_stop_start (test_data );
545+ assert (PyMutex_IsLocked (& test_data -> other -> ob_mutex ));
546+ Py_END_CRITICAL_SECTION2 ();
547+ break ;
548+ case STW_HANDOFF_NESTED :
549+ Py_BEGIN_CRITICAL_SECTION (test_data -> other );
550+ Py_BEGIN_CRITICAL_SECTION (test_data -> obj );
551+ stw_handoff_stop_start (test_data );
552+ Py_END_CRITICAL_SECTION ();
553+ // Ending the inner section resumes the outer one.
554+ assert (PyMutex_IsLocked (& test_data -> other -> ob_mutex ));
555+ Py_END_CRITICAL_SECTION ();
556+ break ;
557+ }
558+
559+ PyGILState_Release (gil );
560+ }
561+
562+ static void
563+ thread_stw_handoff_pinned (void * arg )
564+ {
565+ struct test_data_stw_handoff * test_data = arg ;
566+ PyGILState_STATE gil = PyGILState_Ensure ();
567+
568+ // Wait for the waiter thread to park on the STW mutex.
569+ while (!(_Py_atomic_load_uint8 (& test_data -> stw_mutex -> _bits ) &
570+ _Py_HAS_PARKED )) {
571+ detached_sleep (1 );
572+ }
573+
574+ // Lock the mutex directly so that it stays locked while this thread is
575+ // detached, like a critical section pinned by type_lock_prevent_release().
576+ PyMutex_Lock (& test_data -> obj -> ob_mutex );
577+ _Py_atomic_store_ptr (& test_data -> pinned_tstate , PyThreadState_GET ());
578+
579+ PyInterpreterState * interp = PyInterpreterState_Get ();
580+ _PyEval_StopTheWorld (interp );
581+ _PyEval_StartTheWorld (interp );
582+ PyMutex_Unlock (& test_data -> obj -> ob_mutex );
583+
584+ PyGILState_Release (gil );
585+ }
586+
587+ static PyObject *
588+ stw_handoff_run (int mode )
589+ {
590+ struct test_data_stw_handoff test_data = {
591+ .obj = PyDict_New (),
592+ .other = PyDict_New (),
593+ .inner = PyDict_New (),
594+ .mode = mode ,
595+ .stw_mutex = & PyInterpreterState_Get ()-> stoptheworld .mutex ,
596+ };
597+ if (test_data .obj == NULL || test_data .other == NULL ||
598+ test_data .inner == NULL )
599+ {
600+ goto error ;
601+ }
602+
603+ // Hold the STW mutex so that both threads must wait for it. Avoid
604+ // anything that could stop the world (e.g., allocation triggering a GC)
605+ // until it is released.
606+ PyMutex_Lock (test_data .stw_mutex );
607+
608+ PyThread_handle_t handles [2 ];
609+ PyThread_ident_t idents [2 ];
610+ if (PyThread_start_joinable_thread (& thread_stw_handoff_waiter , & test_data ,
611+ & idents [0 ], & handles [0 ]) != 0 ) {
612+ PyMutex_Unlock (test_data .stw_mutex );
613+ PyErr_SetString (PyExc_RuntimeError , "could not start thread" );
614+ goto error ;
615+ }
616+ if (PyThread_start_joinable_thread (& thread_stw_handoff_pinned , & test_data ,
617+ & idents [1 ], & handles [1 ]) != 0 ) {
618+ Py_FatalError ("could not start thread" );
619+ }
620+
621+ // Wait for the pinned thread to park on the STW mutex. It parks before
622+ // detaching, so once it is detached it is queued behind the waiter.
623+ for (;;) {
624+ PyThreadState * t = _Py_atomic_load_ptr (& test_data .pinned_tstate );
625+ if (t != NULL &&
626+ _Py_atomic_load_int (& t -> state ) == _Py_THREAD_DETACHED ) {
627+ break ;
628+ }
629+ detached_sleep (1 );
630+ }
631+
632+ PyMutex_Unlock (test_data .stw_mutex );
633+
634+ Py_BEGIN_ALLOW_THREADS
635+ PyThread_join_thread (handles [0 ]);
636+ PyThread_join_thread (handles [1 ]);
637+ Py_END_ALLOW_THREADS
638+
639+ Py_DECREF (test_data .obj );
640+ Py_DECREF (test_data .other );
641+ Py_DECREF (test_data .inner );
642+ Py_RETURN_NONE ;
643+
644+ error :
645+ Py_XDECREF (test_data .obj );
646+ Py_XDECREF (test_data .other );
647+ Py_XDECREF (test_data .inner );
648+ return NULL ;
649+ }
650+
651+ static PyObject *
652+ test_critical_section_stw_handoff (PyObject * self , PyObject * Py_UNUSED (args ))
653+ {
654+ return stw_handoff_run (STW_HANDOFF_ONE_MUTEX );
655+ }
656+
657+ static PyObject *
658+ test_critical_section2_stw_handoff (PyObject * self , PyObject * Py_UNUSED (args ))
659+ {
660+ return stw_handoff_run (STW_HANDOFF_TWO_MUTEXES );
661+ }
662+
663+ static PyObject *
664+ test_critical_section_nested_stw_handoff (PyObject * self ,
665+ PyObject * Py_UNUSED (args ))
666+ {
667+ return stw_handoff_run (STW_HANDOFF_NESTED );
668+ }
669+
670+ #endif // Py_GIL_DISABLED && Py_CAN_START_THREADS
671+
467672static PyMethodDef test_methods [] = {
468673 {"test_critical_sections" , test_critical_sections , METH_NOARGS },
469674 {"test_critical_sections_nest" , test_critical_sections_nest , METH_NOARGS },
@@ -476,6 +681,14 @@ static PyMethodDef test_methods[] = {
476681 {"test_critical_sections_threads" , test_critical_sections_threads , METH_NOARGS },
477682 {"test_critical_sections_gc" , test_critical_sections_gc , METH_NOARGS },
478683 {"test_critical_sections_stw" , test_critical_sections_stw , METH_NOARGS },
684+ #endif
685+ #if defined(Py_GIL_DISABLED ) && defined (Py_CAN_START_THREADS )
686+ {"test_critical_section_stw_handoff" ,
687+ test_critical_section_stw_handoff , METH_NOARGS },
688+ {"test_critical_section2_stw_handoff" ,
689+ test_critical_section2_stw_handoff , METH_NOARGS },
690+ {"test_critical_section_nested_stw_handoff" ,
691+ test_critical_section_nested_stw_handoff , METH_NOARGS },
479692#endif
480693 {NULL , NULL } /* sentinel */
481694};
0 commit comments