From 687343300fdfba0dd77fb8ccda4291fc284b97f3 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Sat, 4 Jan 2025 11:43:55 +0000 Subject: [PATCH 1/4] stop the world in all tasks --- Include/internal/pycore_pystate.h | 4 ++-- Modules/_asynciomodule.c | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 1e73e541ef8de0..3812d6def6b6d0 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -182,8 +182,8 @@ extern void _PyEval_StartTheWorldAll(_PyRuntimeState *runtime); // Perform a stop-the-world pause for threads in the specified interpreter. // // NOTE: This is a no-op outside of Py_GIL_DISABLED builds. -extern void _PyEval_StopTheWorld(PyInterpreterState *interp); -extern void _PyEval_StartTheWorld(PyInterpreterState *interp); +extern PyAPI_FUNC(void) _PyEval_StopTheWorld(PyInterpreterState *interp); +extern PyAPI_FUNC(void) _PyEval_StartTheWorld(PyInterpreterState *interp); static inline void diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index b8b184af04a7cb..8763e7592924ba 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3767,9 +3767,11 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) return NULL; } int err = 0; - ASYNCIO_STATE_LOCK(state); - struct llist_node *node; + PyInterpreterState *interp = PyInterpreterState_Get(); + _PyEval_StopTheWorld(interp); + + struct llist_node *node; llist_for_each_safe(node, &state->asyncio_tasks_head) { TaskObj *task = llist_data(node, TaskObj, task_node); if (PyList_Append(tasks, (PyObject *)task) < 0) { @@ -3779,7 +3781,8 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) break; } } - ASYNCIO_STATE_UNLOCK(state); + + _PyEval_StartTheWorld(interp); if (err) { return NULL; } From 078f2d423fd5260d665cfcf071e370a10407f101 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Mon, 6 Jan 2025 11:47:30 +0000 Subject: [PATCH 2/4] add comment --- Modules/_asynciomodule.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 8763e7592924ba..7ba7cf9123dab0 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3768,6 +3768,13 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) } int err = 0; + // The linked list holds borrowed references to the tasks + // so before reading from it, all other threads + // are stopped using stop the world event so that + // no task could be concurrently deallocated while being + // added to the list. + // The state critical section need not to be held as + // all other threads are paused. PyInterpreterState *interp = PyInterpreterState_Get(); _PyEval_StopTheWorld(interp); From 58ea4ee1dd6f71d52a764437e4addc0ad462b077 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Tue, 7 Jan 2025 07:06:53 +0000 Subject: [PATCH 3/4] use _Py_TryIncref to protect against concurrent deallocations --- Include/internal/pycore_object.h | 2 +- Include/internal/pycore_pystate.h | 4 ++-- Modules/_asynciomodule.c | 35 +++++++++++++++---------------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index d7d68f938a9f0a..e26cb7673f939c 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -120,7 +120,7 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc( PyAPI_DATA(Py_ssize_t) _Py_RefTotal; extern void _Py_AddRefTotal(PyThreadState *, Py_ssize_t); -extern void _Py_IncRefTotal(PyThreadState *); +extern PyAPI_FUNC(void) _Py_IncRefTotal(PyThreadState *); extern void _Py_DecRefTotal(PyThreadState *); # define _Py_DEC_REFTOTAL(interp) \ diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 3812d6def6b6d0..1e73e541ef8de0 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -182,8 +182,8 @@ extern void _PyEval_StartTheWorldAll(_PyRuntimeState *runtime); // Perform a stop-the-world pause for threads in the specified interpreter. // // NOTE: This is a no-op outside of Py_GIL_DISABLED builds. -extern PyAPI_FUNC(void) _PyEval_StopTheWorld(PyInterpreterState *interp); -extern PyAPI_FUNC(void) _PyEval_StartTheWorld(PyInterpreterState *interp); +extern void _PyEval_StopTheWorld(PyInterpreterState *interp); +extern void _PyEval_StartTheWorld(PyInterpreterState *interp); static inline void diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 7ba7cf9123dab0..11dab87c63a7bb 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3767,29 +3767,28 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) return NULL; } int err = 0; - - // The linked list holds borrowed references to the tasks - // so before reading from it, all other threads - // are stopped using stop the world event so that - // no task could be concurrently deallocated while being - // added to the list. - // The state critical section need not to be held as - // all other threads are paused. - PyInterpreterState *interp = PyInterpreterState_Get(); - _PyEval_StopTheWorld(interp); - + ASYNCIO_STATE_LOCK(state); struct llist_node *node; + llist_for_each_safe(node, &state->asyncio_tasks_head) { TaskObj *task = llist_data(node, TaskObj, task_node); - if (PyList_Append(tasks, (PyObject *)task) < 0) { - Py_DECREF(tasks); - Py_DECREF(loop); - err = 1; - break; + // The linked list holds borrowed references to task + // as such it is possible that it can concurrently + // deallocated while added to this list. + // To protect against concurrent deallocation, + // we first try to incref the task which would fail + // if it is concurrently getting deallocated in another thread, + // otherwise it gets added to the list. + if (_Py_TryIncref((PyObject *)task)) { + if (_PyList_AppendTakeRef((PyListObject *)tasks, (PyObject *)task) < 0) { + Py_DECREF(tasks); + Py_DECREF(loop); + err = 1; + break; + } } } - - _PyEval_StartTheWorld(interp); + ASYNCIO_STATE_UNLOCK(state); if (err) { return NULL; } From 0b6922e44907eaec778396cf8c1cbf07c0c7f0d1 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Tue, 7 Jan 2025 07:08:27 +0000 Subject: [PATCH 4/4] fix comment --- Modules/_asynciomodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 11dab87c63a7bb..48f0ef95934fa4 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3773,9 +3773,9 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) llist_for_each_safe(node, &state->asyncio_tasks_head) { TaskObj *task = llist_data(node, TaskObj, task_node); // The linked list holds borrowed references to task - // as such it is possible that it can concurrently + // as such it is possible that the task is concurrently // deallocated while added to this list. - // To protect against concurrent deallocation, + // To protect against concurrent deallocations, // we first try to incref the task which would fail // if it is concurrently getting deallocated in another thread, // otherwise it gets added to the list.