gh-120974: Make _asyncio._leave_task atomic in the free-threaded build (#122139)

* gh-120974: Make _asyncio._leave_task atomic in the free-threaded build

Update `_PyDict_DelItemIf` to allow for an argument to be passed to the
predicate.
This commit is contained in:
Sam Gross 2024-07-23 13:06:03 -04:00 committed by GitHub
parent e6b25e9a09
commit a15feded71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 49 additions and 46 deletions

View File

@ -14,8 +14,12 @@ extern "C" {
// Unsafe flavor of PyDict_GetItemWithError(): no error checking // Unsafe flavor of PyDict_GetItemWithError(): no error checking
extern PyObject* _PyDict_GetItemWithError(PyObject *dp, PyObject *key); extern PyObject* _PyDict_GetItemWithError(PyObject *dp, PyObject *key);
extern int _PyDict_DelItemIf(PyObject *mp, PyObject *key, // Delete an item from a dict if a predicate is true
int (*predicate)(PyObject *value)); // Returns -1 on error, 1 if the item was deleted, 0 otherwise
// Export for '_asyncio' shared extension
PyAPI_FUNC(int) _PyDict_DelItemIf(PyObject *mp, PyObject *key,
int (*predicate)(PyObject *value, void *arg),
void *arg);
// "KnownHash" variants // "KnownHash" variants
// Export for '_asyncio' shared extension // Export for '_asyncio' shared extension

View File

@ -1994,30 +1994,36 @@ enter_task(asyncio_state *state, PyObject *loop, PyObject *task)
return 0; return 0;
} }
static int
err_leave_task(PyObject *item, PyObject *task)
{
PyErr_Format(
PyExc_RuntimeError,
"Leaving task %R does not match the current task %R.",
task, item);
return -1;
}
static int
leave_task_predicate(PyObject *item, void *task)
{
if (item != task) {
return err_leave_task(item, (PyObject *)task);
}
return 1;
}
static int static int
leave_task(asyncio_state *state, PyObject *loop, PyObject *task) leave_task(asyncio_state *state, PyObject *loop, PyObject *task)
/*[clinic end generated code: output=0ebf6db4b858fb41 input=51296a46313d1ad8]*/ /*[clinic end generated code: output=0ebf6db4b858fb41 input=51296a46313d1ad8]*/
{ {
PyObject *item; int res = _PyDict_DelItemIf(state->current_tasks, loop,
Py_hash_t hash; leave_task_predicate, task);
hash = PyObject_Hash(loop); if (res == 0) {
if (hash == -1) { // task was not found
return -1; return err_leave_task(Py_None, task);
} }
item = _PyDict_GetItem_KnownHash(state->current_tasks, loop, hash); return res;
if (item != task) {
if (item == NULL) {
/* Not entered, replace with None */
item = Py_None;
}
PyErr_Format(
PyExc_RuntimeError,
"Leaving task %R does not match the current task %R.",
task, item, NULL);
return -1;
}
return _PyDict_DelItem_KnownHash(state->current_tasks, loop, hash);
} }
static PyObject * static PyObject *

View File

@ -31,7 +31,7 @@ _weakref_getweakrefcount_impl(PyObject *module, PyObject *object)
static int static int
is_dead_weakref(PyObject *value) is_dead_weakref(PyObject *value, void *unused)
{ {
if (!PyWeakref_Check(value)) { if (!PyWeakref_Check(value)) {
PyErr_SetString(PyExc_TypeError, "not a weakref"); PyErr_SetString(PyExc_TypeError, "not a weakref");
@ -56,14 +56,7 @@ _weakref__remove_dead_weakref_impl(PyObject *module, PyObject *dct,
PyObject *key) PyObject *key)
/*[clinic end generated code: output=d9ff53061fcb875c input=19fc91f257f96a1d]*/ /*[clinic end generated code: output=d9ff53061fcb875c input=19fc91f257f96a1d]*/
{ {
if (_PyDict_DelItemIf(dct, key, is_dead_weakref) < 0) { if (_PyDict_DelItemIf(dct, key, is_dead_weakref, NULL) < 0) {
if (PyErr_ExceptionMatches(PyExc_KeyError))
/* This function is meant to allow safe weak-value dicts
with GC in another thread (see issue #28427), so it's
ok if the key doesn't exist anymore.
*/
PyErr_Clear();
else
return NULL; return NULL;
} }
Py_RETURN_NONE; Py_RETURN_NONE;

View File

@ -2508,7 +2508,7 @@ delete_index_from_values(PyDictValues *values, Py_ssize_t ix)
values->size = size; values->size = size;
} }
static int static void
delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix, delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
PyObject *old_value, uint64_t new_version) PyObject *old_value, uint64_t new_version)
{ {
@ -2550,7 +2550,6 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
Py_DECREF(old_value); Py_DECREF(old_value);
ASSERT_CONSISTENT(mp); ASSERT_CONSISTENT(mp);
return 0;
} }
int int
@ -2593,7 +2592,8 @@ delitem_knownhash_lock_held(PyObject *op, PyObject *key, Py_hash_t hash)
PyInterpreterState *interp = _PyInterpreterState_GET(); PyInterpreterState *interp = _PyInterpreterState_GET();
uint64_t new_version = _PyDict_NotifyEvent( uint64_t new_version = _PyDict_NotifyEvent(
interp, PyDict_EVENT_DELETED, mp, key, NULL); interp, PyDict_EVENT_DELETED, mp, key, NULL);
return delitem_common(mp, hash, ix, old_value, new_version); delitem_common(mp, hash, ix, old_value, new_version);
return 0;
} }
int int
@ -2608,7 +2608,8 @@ _PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
static int static int
delitemif_lock_held(PyObject *op, PyObject *key, delitemif_lock_held(PyObject *op, PyObject *key,
int (*predicate)(PyObject *value)) int (*predicate)(PyObject *value, void *arg),
void *arg)
{ {
Py_ssize_t ix; Py_ssize_t ix;
PyDictObject *mp; PyDictObject *mp;
@ -2618,24 +2619,20 @@ delitemif_lock_held(PyObject *op, PyObject *key,
ASSERT_DICT_LOCKED(op); ASSERT_DICT_LOCKED(op);
if (!PyDict_Check(op)) {
PyErr_BadInternalCall();
return -1;
}
assert(key); assert(key);
hash = PyObject_Hash(key); hash = PyObject_Hash(key);
if (hash == -1) if (hash == -1)
return -1; return -1;
mp = (PyDictObject *)op; mp = (PyDictObject *)op;
ix = _Py_dict_lookup(mp, key, hash, &old_value); ix = _Py_dict_lookup(mp, key, hash, &old_value);
if (ix == DKIX_ERROR) if (ix == DKIX_ERROR) {
return -1;
if (ix == DKIX_EMPTY || old_value == NULL) {
_PyErr_SetKeyError(key);
return -1; return -1;
} }
if (ix == DKIX_EMPTY || old_value == NULL) {
return 0;
}
res = predicate(old_value); res = predicate(old_value, arg);
if (res == -1) if (res == -1)
return -1; return -1;
@ -2643,7 +2640,8 @@ delitemif_lock_held(PyObject *op, PyObject *key,
PyInterpreterState *interp = _PyInterpreterState_GET(); PyInterpreterState *interp = _PyInterpreterState_GET();
uint64_t new_version = _PyDict_NotifyEvent( uint64_t new_version = _PyDict_NotifyEvent(
interp, PyDict_EVENT_DELETED, mp, key, NULL); interp, PyDict_EVENT_DELETED, mp, key, NULL);
return delitem_common(mp, hash, ix, old_value, new_version); delitem_common(mp, hash, ix, old_value, new_version);
return 1;
} else { } else {
return 0; return 0;
} }
@ -2655,11 +2653,13 @@ delitemif_lock_held(PyObject *op, PyObject *key,
*/ */
int int
_PyDict_DelItemIf(PyObject *op, PyObject *key, _PyDict_DelItemIf(PyObject *op, PyObject *key,
int (*predicate)(PyObject *value)) int (*predicate)(PyObject *value, void *arg),
void *arg)
{ {
assert(PyDict_Check(op));
int res; int res;
Py_BEGIN_CRITICAL_SECTION(op); Py_BEGIN_CRITICAL_SECTION(op);
res = delitemif_lock_held(op, key, predicate); res = delitemif_lock_held(op, key, predicate, arg);
Py_END_CRITICAL_SECTION(); Py_END_CRITICAL_SECTION();
return res; return res;
} }