@@ -1788,13 +1788,6 @@ PyThreadState_Clear(PyThreadState *tstate)
17881788 Py_CLEAR (((_PyThreadStateImpl * )tstate )-> asyncio_running_task );
17891789
17901790
1791- PyMutex_Lock (& tstate -> interp -> asyncio_tasks_lock );
1792- // merge any lingering tasks from thread state to interpreter's
1793- // tasks list
1794- llist_concat (& tstate -> interp -> asyncio_tasks_head ,
1795- & ((_PyThreadStateImpl * )tstate )-> asyncio_tasks_head );
1796- PyMutex_Unlock (& tstate -> interp -> asyncio_tasks_lock );
1797-
17981791 Py_CLEAR (tstate -> dict );
17991792 Py_CLEAR (tstate -> async_exc );
18001793
@@ -1863,11 +1856,30 @@ PyThreadState_Clear(PyThreadState *tstate)
18631856static void
18641857decrement_stoptheworld_countdown (struct _stoptheworld_state * stw );
18651858
1866- /* Common code for PyThreadState_Delete() and PyThreadState_DeleteCurrent() */
1859+ // `release_gil` releases the current thread's GIL. For states already removed
1860+ // by _PyThreadState_RemoveExcept(), `is_unlinked` limits cleanup to task transfer.
18671861static void
1868- tstate_delete_common (PyThreadState * tstate , int release_gil )
1862+ tstate_delete_common (PyThreadState * tstate , int release_gil , int is_unlinked )
18691863{
18701864 assert (tstate -> _status .cleared && !tstate -> _status .finalized );
1865+ assert (!is_unlinked || !release_gil );
1866+
1867+ _PyThreadStateImpl * tstate_impl = (_PyThreadStateImpl * )tstate ;
1868+ // Finalizers may register tasks during or after PyThreadState_Clear().
1869+ // Transfer them before normal unlinking or GIL release: cross-thread task
1870+ // removal uses stop-the-world or the GIL, not asyncio_tasks_lock.
1871+ if (!llist_empty (& tstate_impl -> asyncio_tasks_head )) {
1872+ // Serialize transfers from concurrently exiting threads.
1873+ PyMutex_Lock (& tstate -> interp -> asyncio_tasks_lock );
1874+ llist_concat (& tstate -> interp -> asyncio_tasks_head ,
1875+ & tstate_impl -> asyncio_tasks_head );
1876+ PyMutex_Unlock (& tstate -> interp -> asyncio_tasks_lock );
1877+ }
1878+ assert (llist_empty (& tstate_impl -> asyncio_tasks_head ));
1879+ if (is_unlinked ) {
1880+ return ;
1881+ }
1882+
18711883 tstate_verify_not_active (tstate );
18721884 assert (!_PyThreadState_IsRunningMain (tstate ));
18731885
@@ -1900,7 +1912,6 @@ tstate_delete_common(PyThreadState *tstate, int release_gil)
19001912
19011913#if defined(Py_REF_DEBUG ) && defined(Py_GIL_DISABLED )
19021914 // Add our portion of the total refcount to the interpreter's total.
1903- _PyThreadStateImpl * tstate_impl = (_PyThreadStateImpl * )tstate ;
19041915 tstate -> interp -> object_state .reftotal += tstate_impl -> reftotal ;
19051916 tstate_impl -> reftotal = 0 ;
19061917 assert (tstate_impl -> refcounts .values == NULL );
@@ -1943,7 +1954,7 @@ zapthreads(PyInterpreterState *interp)
19431954 */
19441955 while ((tstate = interp -> threads .head ) != NULL ) {
19451956 tstate_verify_not_active (tstate );
1946- tstate_delete_common (tstate , 0 );
1957+ tstate_delete_common (tstate , /*release_gil=*/ 0 , /*is_unlinked=*/ 0 );
19471958 free_threadstate ((_PyThreadStateImpl * )tstate );
19481959 }
19491960}
@@ -1954,7 +1965,7 @@ PyThreadState_Delete(PyThreadState *tstate)
19541965{
19551966 _Py_EnsureTstateNotNULL (tstate );
19561967 tstate_verify_not_active (tstate );
1957- tstate_delete_common (tstate , 0 );
1968+ tstate_delete_common (tstate , /*release_gil=*/ 0 , /*is_unlinked=*/ 0 );
19581969 free_threadstate ((_PyThreadStateImpl * )tstate );
19591970}
19601971
@@ -1967,7 +1978,7 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate)
19671978 _Py_qsbr_detach (((_PyThreadStateImpl * )tstate )-> qsbr );
19681979#endif
19691980 current_fast_clear (tstate -> interp -> runtime );
1970- tstate_delete_common (tstate , 1 ); // release GIL as part of call
1981+ tstate_delete_common (tstate , /*release_gil=*/ 1 , /*is_unlinked=*/ 0 );
19711982 free_threadstate ((_PyThreadStateImpl * )tstate );
19721983}
19731984
@@ -2037,6 +2048,7 @@ _PyThreadState_DeleteList(PyThreadState *list, int is_after_fork)
20372048 for (p = list ; p ; p = next ) {
20382049 next = p -> next ;
20392050 PyThreadState_Clear (p );
2051+ tstate_delete_common (p , /*release_gil=*/ 0 , /*is_unlinked=*/ 1 );
20402052 if (is_after_fork ) {
20412053 free_threadstate ((_PyThreadStateImpl * )p );
20422054 }
0 commit comments