Skip to content

Commit

Permalink
use _Py_TryIncref to protect against concurrent deallocations
Browse files Browse the repository at this point in the history
  • Loading branch information
kumaraditya303 committed Jan 7, 2025
1 parent 078f2d4 commit 58ea4ee
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 21 deletions.
2 changes: 1 addition & 1 deletion Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
4 changes: 2 additions & 2 deletions Include/internal/pycore_pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 17 additions & 18 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit 58ea4ee

Please sign in to comment.