@@ -1887,7 +1887,10 @@ tstate_delete_common(PyThreadState *tstate, int release_gil)
18871887 if (tstate -> next ) {
18881888 tstate -> next -> prev = tstate -> prev ;
18891889 }
1890- if (tstate -> state != _Py_THREAD_SUSPENDED ) {
1890+ int state = _Py_atomic_load_int_relaxed (& tstate -> state );
1891+ if (state != _Py_THREAD_SUSPENDED &&
1892+ state != _Py_THREAD_SUSPENDED_WAITING )
1893+ {
18911894 // Any ongoing stop-the-world request should not wait for us because
18921895 // our thread is getting deleted.
18931896 if (interp -> stoptheworld .requested ) {
@@ -2160,6 +2163,22 @@ tstate_try_attach(PyThreadState *tstate)
21602163#endif
21612164}
21622165
2166+ static int
2167+ tstate_try_attach_detached (PyThreadState * tstate , int * state )
2168+ {
2169+ #ifdef Py_GIL_DISABLED
2170+ assert (* state == _Py_THREAD_DETACHED ||
2171+ * state == _Py_THREAD_DETACHED_WAITING );
2172+ return _Py_atomic_compare_exchange_int (& tstate -> state ,
2173+ state ,
2174+ _Py_THREAD_ATTACHED );
2175+ #else
2176+ assert (tstate -> state == _Py_THREAD_DETACHED );
2177+ tstate -> state = _Py_THREAD_ATTACHED ;
2178+ return 1 ;
2179+ #endif
2180+ }
2181+
21632182static void
21642183tstate_set_detached (PyThreadState * tstate , int detached_state )
21652184{
@@ -2174,10 +2193,20 @@ tstate_set_detached(PyThreadState *tstate, int detached_state)
21742193static void
21752194tstate_wait_attach (PyThreadState * tstate )
21762195{
2177- do {
2196+ for (;;) {
21782197 int state = _Py_atomic_load_int_relaxed (& tstate -> state );
21792198 if (state == _Py_THREAD_SUSPENDED ) {
2180- // Wait until we're switched out of SUSPENDED to DETACHED.
2199+ // Register an active attach waiter. The next stop-the-world
2200+ // request must let this thread attach before suspending it again.
2201+ if (!_Py_atomic_compare_exchange_int (
2202+ & tstate -> state , & state , _Py_THREAD_SUSPENDED_WAITING ))
2203+ {
2204+ continue ;
2205+ }
2206+ state = _Py_THREAD_SUSPENDED_WAITING ;
2207+ }
2208+ if (state == _Py_THREAD_SUSPENDED_WAITING ) {
2209+ // Park rechecks the state before sleeping, in case we were resumed.
21812210 _PyParkingLot_Park (& tstate -> state , & state , sizeof (tstate -> state ),
21822211 /*timeout=*/ -1 , NULL , /*detach=*/ 0 );
21832212 }
@@ -2186,10 +2215,13 @@ tstate_wait_attach(PyThreadState *tstate)
21862215 _PyThreadState_HangThread (tstate );
21872216 }
21882217 else {
2189- assert (state == _Py_THREAD_DETACHED );
2218+ assert (state == _Py_THREAD_DETACHED ||
2219+ state == _Py_THREAD_DETACHED_WAITING );
2220+ if (tstate_try_attach_detached (tstate , & state )) {
2221+ return ;
2222+ }
21902223 }
2191- // Once we're back in DETACHED we can re-attach
2192- } while (!tstate_try_attach (tstate ));
2224+ }
21932225}
21942226
21952227void
@@ -2354,6 +2386,8 @@ park_detached_threads(struct _stoptheworld_state *stw)
23542386 _Py_FOR_EACH_STW_INTERP (stw , i ) {
23552387 _Py_FOR_EACH_TSTATE_UNLOCKED (i , t ) {
23562388 int state = _Py_atomic_load_int_relaxed (& t -> state );
2389+ // DETACHED_WAITING threads remain counted until they attach and
2390+ // stop, so repeated pauses cannot prevent them from attaching.
23572391 if (state == _Py_THREAD_DETACHED ) {
23582392 // Atomically transition to "suspended" if in "detached" state.
23592393 if (_Py_atomic_compare_exchange_int (
@@ -2428,6 +2462,30 @@ stop_the_world(struct _stoptheworld_state *stw)
24282462 stw -> world_stopped = 1 ;
24292463}
24302464
2465+ static void
2466+ tstate_resume (PyThreadState * tstate )
2467+ {
2468+ assert (tstate != _PyThreadState_GET ());
2469+ int state = _Py_atomic_load_int_relaxed (& tstate -> state );
2470+ int next_state ;
2471+ do {
2472+ assert (state == _Py_THREAD_SUSPENDED ||
2473+ state == _Py_THREAD_SUSPENDED_WAITING );
2474+ if (state == _Py_THREAD_SUSPENDED_WAITING ) {
2475+ next_state = _Py_THREAD_DETACHED_WAITING ;
2476+ }
2477+ else {
2478+ next_state = _Py_THREAD_DETACHED ;
2479+ }
2480+ // Retry if an attach waiter registered concurrently.
2481+ } while (!_Py_atomic_compare_exchange_int (
2482+ & tstate -> state , & state , next_state ));
2483+ // Wake the thread if it is parked in tstate_wait_attach().
2484+ if (state == _Py_THREAD_SUSPENDED_WAITING ) {
2485+ _PyParkingLot_UnparkAll (& tstate -> state );
2486+ }
2487+ }
2488+
24312489static void
24322490start_the_world (struct _stoptheworld_state * stw )
24332491{
@@ -2441,10 +2499,7 @@ start_the_world(struct _stoptheworld_state *stw)
24412499 _Py_FOR_EACH_STW_INTERP (stw , i ) {
24422500 _Py_FOR_EACH_TSTATE_UNLOCKED (i , t ) {
24432501 if (t != stw -> requester ) {
2444- assert (_Py_atomic_load_int_relaxed (& t -> state ) ==
2445- _Py_THREAD_SUSPENDED );
2446- _Py_atomic_store_int (& t -> state , _Py_THREAD_DETACHED );
2447- _PyParkingLot_UnparkAll (& t -> state );
2502+ tstate_resume (t );
24482503 }
24492504 }
24502505 }
0 commit comments