@@ -1837,13 +1837,6 @@ PyThreadState_Clear(PyThreadState *tstate)
18371837 Py_CLEAR (((_PyThreadStateImpl * )tstate )-> asyncio_running_task );
18381838
18391839
1840- PyMutex_Lock (& tstate -> interp -> asyncio_tasks_lock );
1841- // merge any lingering tasks from thread state to interpreter's
1842- // tasks list
1843- llist_concat (& tstate -> interp -> asyncio_tasks_head ,
1844- & ((_PyThreadStateImpl * )tstate )-> asyncio_tasks_head );
1845- PyMutex_Unlock (& tstate -> interp -> asyncio_tasks_lock );
1846-
18471840 Py_CLEAR (tstate -> dict );
18481841 Py_CLEAR (tstate -> async_exc );
18491842
@@ -1919,11 +1912,30 @@ PyThreadState_Clear(PyThreadState *tstate)
19191912static void
19201913decrement_stoptheworld_countdown (struct _stoptheworld_state * stw );
19211914
1922- /* Common code for PyThreadState_Delete() and PyThreadState_DeleteCurrent() */
1915+ // `release_gil` releases the current thread's GIL. For states already removed
1916+ // by _PyThreadState_RemoveExcept(), `is_unlinked` limits cleanup to task transfer.
19231917static void
1924- tstate_delete_common (PyThreadState * tstate , int release_gil )
1918+ tstate_delete_common (PyThreadState * tstate , int release_gil , int is_unlinked )
19251919{
19261920 assert (tstate -> _status .cleared && !tstate -> _status .finalized );
1921+ assert (!is_unlinked || !release_gil );
1922+
1923+ _PyThreadStateImpl * tstate_impl = (_PyThreadStateImpl * )tstate ;
1924+ // Finalizers may register tasks during or after PyThreadState_Clear().
1925+ // Transfer them before normal unlinking or GIL release: cross-thread task
1926+ // removal uses stop-the-world or the GIL, not asyncio_tasks_lock.
1927+ if (!llist_empty (& tstate_impl -> asyncio_tasks_head )) {
1928+ // Serialize transfers from concurrently exiting threads.
1929+ PyMutex_Lock (& tstate -> interp -> asyncio_tasks_lock );
1930+ llist_concat (& tstate -> interp -> asyncio_tasks_head ,
1931+ & tstate_impl -> asyncio_tasks_head );
1932+ PyMutex_Unlock (& tstate -> interp -> asyncio_tasks_lock );
1933+ }
1934+ assert (llist_empty (& tstate_impl -> asyncio_tasks_head ));
1935+ if (is_unlinked ) {
1936+ return ;
1937+ }
1938+
19271939 tstate_verify_not_active (tstate );
19281940 assert (!_PyThreadState_IsRunningMain (tstate ));
19291941
@@ -1959,7 +1971,6 @@ tstate_delete_common(PyThreadState *tstate, int release_gil)
19591971
19601972#if defined(Py_REF_DEBUG ) && defined(Py_GIL_DISABLED )
19611973 // Add our portion of the total refcount to the interpreter's total.
1962- _PyThreadStateImpl * tstate_impl = (_PyThreadStateImpl * )tstate ;
19631974 tstate -> interp -> object_state .reftotal += tstate_impl -> reftotal ;
19641975 tstate_impl -> reftotal = 0 ;
19651976 assert (tstate_impl -> refcounts .values == NULL );
@@ -2006,7 +2017,7 @@ zapthreads(PyInterpreterState *interp)
20062017 */
20072018 while ((tstate = interp -> threads .head ) != NULL ) {
20082019 tstate_verify_not_active (tstate );
2009- tstate_delete_common (tstate , 0 );
2020+ tstate_delete_common (tstate , /*release_gil=*/ 0 , /*is_unlinked=*/ 0 );
20102021 free_threadstate ((_PyThreadStateImpl * )tstate );
20112022 }
20122023}
@@ -2017,7 +2028,7 @@ PyThreadState_Delete(PyThreadState *tstate)
20172028{
20182029 _Py_EnsureTstateNotNULL (tstate );
20192030 tstate_verify_not_active (tstate );
2020- tstate_delete_common (tstate , 0 );
2031+ tstate_delete_common (tstate , /*release_gil=*/ 0 , /*is_unlinked=*/ 0 );
20212032 free_threadstate ((_PyThreadStateImpl * )tstate );
20222033}
20232034
@@ -2033,7 +2044,7 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate)
20332044 _PyStats_Detach ((_PyThreadStateImpl * )tstate );
20342045#endif
20352046 current_fast_clear (tstate -> interp -> runtime );
2036- tstate_delete_common (tstate , 1 ); // release GIL as part of call
2047+ tstate_delete_common (tstate , /*release_gil=*/ 1 , /*is_unlinked=*/ 0 );
20372048 free_threadstate ((_PyThreadStateImpl * )tstate );
20382049}
20392050
@@ -2103,6 +2114,7 @@ _PyThreadState_DeleteList(PyThreadState *list, int is_after_fork)
21032114 for (p = list ; p ; p = next ) {
21042115 next = p -> next ;
21052116 PyThreadState_Clear (p );
2117+ tstate_delete_common (p , /*release_gil=*/ 0 , /*is_unlinked=*/ 1 );
21062118 if (is_after_fork ) {
21072119 free_threadstate ((_PyThreadStateImpl * )p );
21082120 }
0 commit comments