Skip to content

Commit ea1160c

Browse files
committed
asyncio: fix race conditions in enter_task and leave_task
The fix relies on the internal locking of PyDictObject. It would be nice, in the future, to use an external lock or move the store task to the loop object.
1 parent 07f5f8c commit ea1160c

File tree

1 file changed

+40
-27
lines changed

1 file changed

+40
-27
lines changed

Modules/_asynciomodule.c

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,15 +1969,13 @@ unregister_task(asyncio_state *state, PyObject *task)
19691969
static int
19701970
enter_task(asyncio_state *state, PyObject *loop, PyObject *task)
19711971
{
1972-
PyObject *item;
1973-
Py_hash_t hash;
1974-
hash = PyObject_Hash(loop);
1975-
if (hash == -1) {
1972+
int is_insert;
1973+
PyObject *item = _PyDict_SetDefault(state->current_tasks, loop, task, 1,
1974+
&is_insert);
1975+
if (item == NULL) {
19761976
return -1;
19771977
}
1978-
item = _PyDict_GetItem_KnownHash(state->current_tasks, loop, hash);
1979-
if (item != NULL) {
1980-
Py_INCREF(item);
1978+
if (!is_insert) {
19811979
PyErr_Format(
19821980
PyExc_RuntimeError,
19831981
"Cannot enter into task %R while another " \
@@ -1986,36 +1984,51 @@ enter_task(asyncio_state *state, PyObject *loop, PyObject *task)
19861984
Py_DECREF(item);
19871985
return -1;
19881986
}
1989-
if (PyErr_Occurred()) {
1990-
return -1;
1991-
}
1992-
return _PyDict_SetItem_KnownHash(state->current_tasks, loop, task, hash);
1987+
assert(item == task);
1988+
Py_DECREF(item);
1989+
return 0;
19931990
}
19941991

1992+
struct task_matches_arg {
1993+
PyObject *task;
1994+
PyObject *item;
1995+
};
1996+
1997+
static int
1998+
task_matches(PyObject *item, void *data)
1999+
{
2000+
struct task_matches_arg *arg = (struct task_matches_arg *)data;
2001+
arg->item = Py_NewRef(item);
2002+
return arg->task == item;
2003+
}
19952004

19962005
static int
19972006
leave_task(asyncio_state *state, PyObject *loop, PyObject *task)
19982007
/*[clinic end generated code: output=0ebf6db4b858fb41 input=51296a46313d1ad8]*/
19992008
{
2000-
PyObject *item;
2001-
Py_hash_t hash;
2002-
hash = PyObject_Hash(loop);
2003-
if (hash == -1) {
2004-
return -1;
2005-
}
2006-
item = _PyDict_GetItem_KnownHash(state->current_tasks, loop, hash);
2007-
if (item != task) {
2008-
if (item == NULL) {
2009-
/* Not entered, replace with None */
2010-
item = Py_None;
2009+
struct task_matches_arg arg;
2010+
arg.task = task;
2011+
arg.item = Py_None;
2012+
int res = _PyDict_DelItemIf(state->current_tasks, loop, &task_matches, &arg);
2013+
if (res != 0) {
2014+
if (PyErr_ExceptionMatches(PyExc_KeyError)) {
2015+
// use custom error message if loop is not present
2016+
goto fail;
20112017
}
2012-
PyErr_Format(
2013-
PyExc_RuntimeError,
2014-
"Leaving task %R does not match the current task %R.",
2015-
task, item, NULL);
20162018
return -1;
20172019
}
2018-
return _PyDict_DelItem_KnownHash(state->current_tasks, loop, hash);
2020+
if (arg.item == task) {
2021+
Py_XDECREF(arg.item);
2022+
return 0;
2023+
}
2024+
2025+
fail:
2026+
PyErr_Format(
2027+
PyExc_RuntimeError,
2028+
"Leaving task %R does not match the current task %R.",
2029+
task, arg.item, NULL);
2030+
Py_XDECREF(arg.item);
2031+
return -1;
20192032
}
20202033

20212034
/* ----- Task */

0 commit comments

Comments
 (0)