@@ -1908,7 +1908,10 @@ tstate_delete_common(PyThreadState *tstate, int release_gil)
19081908 if (tstate -> next ) {
19091909 tstate -> next -> prev = tstate -> prev ;
19101910 }
1911- if (tstate -> state != _Py_THREAD_SUSPENDED ) {
1911+ int state = _Py_atomic_load_int_relaxed (& tstate -> state );
1912+ if (state != _Py_THREAD_SUSPENDED &&
1913+ state != _Py_THREAD_SUSPENDED_WAITING )
1914+ {
19121915 // Any ongoing stop-the-world request should not wait for us because
19131916 // our thread is getting deleted.
19141917 if (interp -> stoptheworld .requested ) {
@@ -2192,6 +2195,22 @@ tstate_try_attach(PyThreadState *tstate)
21922195#endif
21932196}
21942197
2198+ static int
2199+ tstate_try_attach_detached (PyThreadState * tstate , int * state )
2200+ {
2201+ #ifdef Py_GIL_DISABLED
2202+ assert (* state == _Py_THREAD_DETACHED ||
2203+ * state == _Py_THREAD_DETACHED_WAITING );
2204+ return _Py_atomic_compare_exchange_int (& tstate -> state ,
2205+ state ,
2206+ _Py_THREAD_ATTACHED );
2207+ #else
2208+ assert (tstate -> state == _Py_THREAD_DETACHED );
2209+ tstate -> state = _Py_THREAD_ATTACHED ;
2210+ return 1 ;
2211+ #endif
2212+ }
2213+
21952214static void
21962215tstate_set_detached (PyThreadState * tstate , int detached_state )
21972216{
@@ -2206,10 +2225,20 @@ tstate_set_detached(PyThreadState *tstate, int detached_state)
22062225static void
22072226tstate_wait_attach (PyThreadState * tstate )
22082227{
2209- do {
2228+ for (;;) {
22102229 int state = _Py_atomic_load_int_relaxed (& tstate -> state );
22112230 if (state == _Py_THREAD_SUSPENDED ) {
2212- // Wait until we're switched out of SUSPENDED to DETACHED.
2231+ // Register an active attach waiter. The next stop-the-world
2232+ // request must let this thread attach before suspending it again.
2233+ if (!_Py_atomic_compare_exchange_int (
2234+ & tstate -> state , & state , _Py_THREAD_SUSPENDED_WAITING ))
2235+ {
2236+ continue ;
2237+ }
2238+ state = _Py_THREAD_SUSPENDED_WAITING ;
2239+ }
2240+ if (state == _Py_THREAD_SUSPENDED_WAITING ) {
2241+ // Park rechecks the state before sleeping, in case we were resumed.
22132242 _PyParkingLot_Park (& tstate -> state , & state , sizeof (tstate -> state ),
22142243 /*timeout=*/ -1 , NULL , /*detach=*/ 0 );
22152244 }
@@ -2218,10 +2247,13 @@ tstate_wait_attach(PyThreadState *tstate)
22182247 _PyThreadState_HangThread (tstate );
22192248 }
22202249 else {
2221- assert (state == _Py_THREAD_DETACHED );
2250+ assert (state == _Py_THREAD_DETACHED ||
2251+ state == _Py_THREAD_DETACHED_WAITING );
2252+ if (tstate_try_attach_detached (tstate , & state )) {
2253+ return ;
2254+ }
22222255 }
2223- // Once we're back in DETACHED we can re-attach
2224- } while (!tstate_try_attach (tstate ));
2256+ }
22252257}
22262258
22272259void
@@ -2390,6 +2422,8 @@ park_detached_threads(struct _stoptheworld_state *stw)
23902422 _Py_FOR_EACH_STW_INTERP (stw , i ) {
23912423 _Py_FOR_EACH_TSTATE_UNLOCKED (i , t ) {
23922424 int state = _Py_atomic_load_int_relaxed (& t -> state );
2425+ // DETACHED_WAITING threads remain counted until they attach and
2426+ // stop, so repeated pauses cannot prevent them from attaching.
23932427 if (state == _Py_THREAD_DETACHED ) {
23942428 // Atomically transition to "suspended" if in "detached" state.
23952429 if (_Py_atomic_compare_exchange_int (
@@ -2465,6 +2499,30 @@ stop_the_world(struct _stoptheworld_state *stw)
24652499 stw -> world_stopped = 1 ;
24662500}
24672501
2502+ static void
2503+ tstate_resume (PyThreadState * tstate )
2504+ {
2505+ assert (tstate != _PyThreadState_GET ());
2506+ int state = _Py_atomic_load_int_relaxed (& tstate -> state );
2507+ int next_state ;
2508+ do {
2509+ assert (state == _Py_THREAD_SUSPENDED ||
2510+ state == _Py_THREAD_SUSPENDED_WAITING );
2511+ if (state == _Py_THREAD_SUSPENDED_WAITING ) {
2512+ next_state = _Py_THREAD_DETACHED_WAITING ;
2513+ }
2514+ else {
2515+ next_state = _Py_THREAD_DETACHED ;
2516+ }
2517+ // Retry if an attach waiter registered concurrently.
2518+ } while (!_Py_atomic_compare_exchange_int (
2519+ & tstate -> state , & state , next_state ));
2520+ // Wake the thread if it is parked in tstate_wait_attach().
2521+ if (state == _Py_THREAD_SUSPENDED_WAITING ) {
2522+ _PyParkingLot_UnparkAll (& tstate -> state );
2523+ }
2524+ }
2525+
24682526static void
24692527start_the_world (struct _stoptheworld_state * stw )
24702528{
@@ -2478,10 +2536,7 @@ start_the_world(struct _stoptheworld_state *stw)
24782536 _Py_FOR_EACH_STW_INTERP (stw , i ) {
24792537 _Py_FOR_EACH_TSTATE_UNLOCKED (i , t ) {
24802538 if (t != stw -> requester ) {
2481- assert (_Py_atomic_load_int_relaxed (& t -> state ) ==
2482- _Py_THREAD_SUSPENDED );
2483- _Py_atomic_store_int (& t -> state , _Py_THREAD_DETACHED );
2484- _PyParkingLot_UnparkAll (& t -> state );
2539+ tstate_resume (t );
24852540 }
24862541 }
24872542 }
0 commit comments