Skip to content

Commit ddf0d98

Browse files
committed
gh-159041: Strengthen asyncio task cleanup checks
1 parent bb63e8c commit ddf0d98

2 files changed

Lines changed: 17 additions & 6 deletions

File tree

‎Lib/test/test_asyncio/test_tasks.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3102,6 +3102,7 @@ def test_task_created_during_thread_state_clear(self):
31023102
loop = self.loop
31033103
task = None
31043104
var = contextvars.ContextVar('var')
3105+
# Avoid recreating the thread state's context after it is cleared.
31053106
task_context = contextvars.Context()
31063107

31073108
async def noop():
@@ -3113,11 +3114,15 @@ def __del__(self):
31133114
task = loop.create_task(noop(), context=task_context)
31143115

31153116
def create_finalizer():
3117+
# gh-159041: Run the finalizer when PyThreadState_Clear()
3118+
# clears the thread's context.
31163119
var.set(CreatesTaskOnClear())
31173120

3121+
# threading.Thread owns its Context, so use _thread to make the
3122+
# Context lifetime match the thread state's lifetime.
31183123
handle = _thread.start_joinable_thread(create_finalizer)
31193124
handle.join(support.SHORT_TIMEOUT)
3120-
self.assertTrue(handle.is_done())
3125+
self.assertTrue(handle.is_done(), 'thread failed to exit')
31213126
self.assertIsNotNone(task)
31223127

31233128
try:

‎Python/pystate.c‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -815,9 +815,14 @@ common_constants_clear(PyInterpreterState *interp)
815815
static void
816816
move_asyncio_tasks_to_interpreter(PyThreadState *tstate)
817817
{
818+
_PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
819+
if (llist_empty(&tstate_impl->asyncio_tasks_head)) {
820+
return;
821+
}
822+
818823
PyMutex_Lock(&tstate->interp->asyncio_tasks_lock);
819824
llist_concat(&tstate->interp->asyncio_tasks_head,
820-
&((_PyThreadStateImpl *)tstate)->asyncio_tasks_head);
825+
&tstate_impl->asyncio_tasks_head);
821826
PyMutex_Unlock(&tstate->interp->asyncio_tasks_lock);
822827
}
823828

@@ -961,12 +966,11 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
961966
#endif
962967

963968
if (tstate->interp == interp) {
969+
// gh-159041: Finalizers above may have registered new asyncio tasks.
970+
move_asyncio_tasks_to_interpreter(tstate);
971+
964972
/* We are now safe to fix tstate->_status.cleared. */
965973
// XXX Do this (much) earlier?
966-
// Finalizers above may have registered new asyncio tasks.
967-
move_asyncio_tasks_to_interpreter(tstate);
968-
assert(llist_empty(
969-
&((_PyThreadStateImpl *)tstate)->asyncio_tasks_head));
970974
tstate->_status.cleared = 1;
971975
}
972976

@@ -1936,6 +1940,8 @@ static void
19361940
tstate_delete_common(PyThreadState *tstate, int release_gil)
19371941
{
19381942
assert(tstate->_status.cleared && !tstate->_status.finalized);
1943+
assert(llist_empty(
1944+
&((_PyThreadStateImpl *)tstate)->asyncio_tasks_head));
19391945
tstate_verify_not_active(tstate);
19401946
assert(!_PyThreadState_IsRunningMain(tstate));
19411947

0 commit comments

Comments
 (0)