@@ -1474,6 +1474,15 @@ free_threadstate(_PyThreadStateImpl *tstate)
14741474 }
14751475}
14761476
1477+ static void
1478+ decref_threadstate (_PyThreadStateImpl * tstate )
1479+ {
1480+ if (_Py_atomic_add_ssize (& tstate -> refcount , -1 ) == 1 ) {
1481+ // The last reference to the thread state is gone.
1482+ free_threadstate (tstate );
1483+ }
1484+ }
1485+
14771486/* Get the thread state to a minimal consistent state.
14781487 Further init happens in pylifecycle.c before it can be used.
14791488 All fields not initialized here are expected to be zeroed out,
@@ -1938,8 +1947,12 @@ _PyThreadState_RemoveExcept(PyThreadState *tstate)
19381947// Deletes the thread states in the linked list `list`.
19391948//
19401949// This is intended to be used in conjunction with _PyThreadState_RemoveExcept.
1950+ //
1951+ // If `is_after_fork` is true, the thread states are immediately freed.
1952+ // Otherwise, they are decref'd because they may still be referenced by an
1953+ // OS thread.
19411954void
1942- _PyThreadState_DeleteList (PyThreadState * list )
1955+ _PyThreadState_DeleteList (PyThreadState * list , int is_after_fork )
19431956{
19441957 // The world can't be stopped because we PyThreadState_Clear() can
19451958 // call destructors.
@@ -1949,7 +1962,12 @@ _PyThreadState_DeleteList(PyThreadState *list)
19491962 for (p = list ; p ; p = next ) {
19501963 next = p -> next ;
19511964 PyThreadState_Clear (p );
1952- free_threadstate ((_PyThreadStateImpl * )p );
1965+ if (is_after_fork ) {
1966+ free_threadstate ((_PyThreadStateImpl * )p );
1967+ }
1968+ else {
1969+ decref_threadstate ((_PyThreadStateImpl * )p );
1970+ }
19531971 }
19541972}
19551973
@@ -2082,12 +2100,19 @@ static void
20822100tstate_wait_attach (PyThreadState * tstate )
20832101{
20842102 do {
2085- int expected = _Py_THREAD_SUSPENDED ;
2086-
2087- // Wait until we're switched out of SUSPENDED to DETACHED.
2088- _PyParkingLot_Park (& tstate -> state , & expected , sizeof (tstate -> state ),
2089- /*timeout=*/ -1 , NULL , /*detach=*/ 0 );
2090-
2103+ int state = _Py_atomic_load_int_relaxed (& tstate -> state );
2104+ if (state == _Py_THREAD_SUSPENDED ) {
2105+ // Wait until we're switched out of SUSPENDED to DETACHED.
2106+ _PyParkingLot_Park (& tstate -> state , & state , sizeof (tstate -> state ),
2107+ /*timeout=*/ -1 , NULL , /*detach=*/ 0 );
2108+ }
2109+ else if (state == _Py_THREAD_SHUTTING_DOWN ) {
2110+ // We're shutting down, so we can't attach.
2111+ _PyThreadState_HangThread (tstate );
2112+ }
2113+ else {
2114+ assert (state == _Py_THREAD_DETACHED );
2115+ }
20912116 // Once we're back in DETACHED we can re-attach
20922117 } while (!tstate_try_attach (tstate ));
20932118}
@@ -2118,7 +2143,7 @@ _PyThreadState_Attach(PyThreadState *tstate)
21182143 tstate_activate (tstate );
21192144
21202145#ifdef Py_GIL_DISABLED
2121- if (_PyEval_IsGILEnabled (tstate ) && !tstate -> _status . holds_gil ) {
2146+ if (_PyEval_IsGILEnabled (tstate ) && !tstate -> holds_gil ) {
21222147 // The GIL was enabled between our call to _PyEval_AcquireLock()
21232148 // and when we attached (the GIL can't go from enabled to disabled
21242149 // here because only a thread holding the GIL can disable
@@ -2201,6 +2226,15 @@ _PyThreadState_Suspend(PyThreadState *tstate)
22012226 HEAD_UNLOCK (runtime );
22022227}
22032228
2229+ void
2230+ _PyThreadState_SetShuttingDown (PyThreadState * tstate )
2231+ {
2232+ _Py_atomic_store_int (& tstate -> state , _Py_THREAD_SHUTTING_DOWN );
2233+ #ifdef Py_GIL_DISABLED
2234+ _PyParkingLot_UnparkAll (& tstate -> state );
2235+ #endif
2236+ }
2237+
22042238// Decrease stop-the-world counter of remaining number of threads that need to
22052239// pause. If we are the final thread to pause, notify the requesting thread.
22062240static void
@@ -3001,43 +3035,27 @@ _PyThreadState_CheckConsistency(PyThreadState *tstate)
30013035#endif
30023036
30033037
3004- // Check if a Python thread must exit immediately, rather than taking the GIL
3005- // if Py_Finalize() has been called.
3038+ // Check if a Python thread must call _PyThreadState_HangThread(), rather than
3039+ // taking the GIL or attaching to the interpreter if Py_Finalize() has been
3040+ // called.
30063041//
30073042// When this function is called by a daemon thread after Py_Finalize() has been
3008- // called, the GIL does no longer exist.
3009- //
3010- // tstate can be a dangling pointer (point to freed memory): only tstate value
3011- // is used, the pointer is not deferenced.
3043+ // called, the GIL may no longer exist.
30123044//
30133045// tstate must be non-NULL.
30143046int
30153047_PyThreadState_MustExit (PyThreadState * tstate )
30163048{
3017- /* bpo-39877: Access _PyRuntime directly rather than using
3018- tstate->interp->runtime to support calls from Python daemon threads.
3019- After Py_Finalize() has been called, tstate can be a dangling pointer:
3020- point to PyThreadState freed memory. */
3021- unsigned long finalizing_id = _PyRuntimeState_GetFinalizingID (& _PyRuntime );
3022- PyThreadState * finalizing = _PyRuntimeState_GetFinalizing (& _PyRuntime );
3023- if (finalizing == NULL ) {
3024- // XXX This isn't completely safe from daemon thraeds,
3025- // since tstate might be a dangling pointer.
3026- finalizing = _PyInterpreterState_GetFinalizing (tstate -> interp );
3027- finalizing_id = _PyInterpreterState_GetFinalizingID (tstate -> interp );
3028- }
3029- // XXX else check &_PyRuntime._main_interpreter._initial_thread
3030- if (finalizing == NULL ) {
3031- return 0 ;
3032- }
3033- else if (finalizing == tstate ) {
3034- return 0 ;
3035- }
3036- else if (finalizing_id == PyThread_get_thread_ident ()) {
3037- /* gh-109793: we must have switched interpreters. */
3038- return 0 ;
3039- }
3040- return 1 ;
3049+ int state = _Py_atomic_load_int_relaxed (& tstate -> state );
3050+ return state == _Py_THREAD_SHUTTING_DOWN ;
3051+ }
3052+
3053+ void
3054+ _PyThreadState_HangThread (PyThreadState * tstate )
3055+ {
3056+ _PyThreadStateImpl * tstate_impl = (_PyThreadStateImpl * )tstate ;
3057+ decref_threadstate (tstate_impl );
3058+ PyThread_hang_thread ();
30413059}
30423060
30433061/********************/
0 commit comments