Skip to content

Commit 94d7cd4

Browse files
colesburymiss-islington
authored andcommitted
pythongh-120974: Make _asyncio._leave_task atomic in the free-threaded build (pythonGH-122139)
* pythongh-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. (cherry picked from commit a15fede) Co-authored-by: Sam Gross <[email protected]>
1 parent 214b430 commit 94d7cd4

File tree

4 files changed

+48
-45
lines changed

4 files changed

+48
-45
lines changed

Include/internal/pycore_dict.h

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ extern "C" {
1616
// Unsafe flavor of PyDict_GetItemWithError(): no error checking
1717
extern PyObject* _PyDict_GetItemWithError(PyObject *dp, PyObject *key);
1818

19-
extern int _PyDict_DelItemIf(PyObject *mp, PyObject *key,
20-
int (*predicate)(PyObject *value));
19+
// Delete an item from a dict if a predicate is true
20+
// Returns -1 on error, 1 if the item was deleted, 0 otherwise
21+
// Export for '_asyncio' shared extension
22+
PyAPI_FUNC(int) _PyDict_DelItemIf(PyObject *mp, PyObject *key,
23+
int (*predicate)(PyObject *value, void *arg),
24+
void *arg);
2125

2226
// "KnownHash" variants
2327
// Export for '_asyncio' shared extension

Modules/_asynciomodule.c

+24-18
Original file line numberDiff line numberDiff line change
@@ -1945,30 +1945,36 @@ enter_task(asyncio_state *state, PyObject *loop, PyObject *task)
19451945
return 0;
19461946
}
19471947

1948+
static int
1949+
err_leave_task(PyObject *item, PyObject *task)
1950+
{
1951+
PyErr_Format(
1952+
PyExc_RuntimeError,
1953+
"Leaving task %R does not match the current task %R.",
1954+
task, item);
1955+
return -1;
1956+
}
1957+
1958+
static int
1959+
leave_task_predicate(PyObject *item, void *task)
1960+
{
1961+
if (item != task) {
1962+
return err_leave_task(item, (PyObject *)task);
1963+
}
1964+
return 1;
1965+
}
19481966

19491967
static int
19501968
leave_task(asyncio_state *state, PyObject *loop, PyObject *task)
19511969
/*[clinic end generated code: output=0ebf6db4b858fb41 input=51296a46313d1ad8]*/
19521970
{
1953-
PyObject *item;
1954-
Py_hash_t hash;
1955-
hash = PyObject_Hash(loop);
1956-
if (hash == -1) {
1957-
return -1;
1958-
}
1959-
item = _PyDict_GetItem_KnownHash(state->current_tasks, loop, hash);
1960-
if (item != task) {
1961-
if (item == NULL) {
1962-
/* Not entered, replace with None */
1963-
item = Py_None;
1964-
}
1965-
PyErr_Format(
1966-
PyExc_RuntimeError,
1967-
"Leaving task %R does not match the current task %R.",
1968-
task, item, NULL);
1969-
return -1;
1971+
int res = _PyDict_DelItemIf(state->current_tasks, loop,
1972+
leave_task_predicate, task);
1973+
if (res == 0) {
1974+
// task was not found
1975+
return err_leave_task(Py_None, task);
19701976
}
1971-
return _PyDict_DelItem_KnownHash(state->current_tasks, loop, hash);
1977+
return res;
19721978
}
19731979

19741980
static PyObject *

Modules/_weakref.c

+3-10
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ _weakref_getweakrefcount_impl(PyObject *module, PyObject *object)
3131

3232

3333
static int
34-
is_dead_weakref(PyObject *value)
34+
is_dead_weakref(PyObject *value, void *unused)
3535
{
3636
if (!PyWeakref_Check(value)) {
3737
PyErr_SetString(PyExc_TypeError, "not a weakref");
@@ -56,15 +56,8 @@ _weakref__remove_dead_weakref_impl(PyObject *module, PyObject *dct,
5656
PyObject *key)
5757
/*[clinic end generated code: output=d9ff53061fcb875c input=19fc91f257f96a1d]*/
5858
{
59-
if (_PyDict_DelItemIf(dct, key, is_dead_weakref) < 0) {
60-
if (PyErr_ExceptionMatches(PyExc_KeyError))
61-
/* This function is meant to allow safe weak-value dicts
62-
with GC in another thread (see issue #28427), so it's
63-
ok if the key doesn't exist anymore.
64-
*/
65-
PyErr_Clear();
66-
else
67-
return NULL;
59+
if (_PyDict_DelItemIf(dct, key, is_dead_weakref, NULL) < 0) {
60+
return NULL;
6861
}
6962
Py_RETURN_NONE;
7063
}

Objects/dictobject.c

+15-15
Original file line numberDiff line numberDiff line change
@@ -2567,7 +2567,7 @@ delete_index_from_values(PyDictValues *values, Py_ssize_t ix)
25672567
values->size = size;
25682568
}
25692569

2570-
static int
2570+
static void
25712571
delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
25722572
PyObject *old_value, uint64_t new_version)
25732573
{
@@ -2609,7 +2609,6 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
26092609
Py_DECREF(old_value);
26102610

26112611
ASSERT_CONSISTENT(mp);
2612-
return 0;
26132612
}
26142613

26152614
int
@@ -2652,7 +2651,8 @@ delitem_knownhash_lock_held(PyObject *op, PyObject *key, Py_hash_t hash)
26522651
PyInterpreterState *interp = _PyInterpreterState_GET();
26532652
uint64_t new_version = _PyDict_NotifyEvent(
26542653
interp, PyDict_EVENT_DELETED, mp, key, NULL);
2655-
return delitem_common(mp, hash, ix, old_value, new_version);
2654+
delitem_common(mp, hash, ix, old_value, new_version);
2655+
return 0;
26562656
}
26572657

26582658
int
@@ -2667,7 +2667,8 @@ _PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
26672667

26682668
static int
26692669
delitemif_lock_held(PyObject *op, PyObject *key,
2670-
int (*predicate)(PyObject *value))
2670+
int (*predicate)(PyObject *value, void *arg),
2671+
void *arg)
26712672
{
26722673
Py_ssize_t ix;
26732674
PyDictObject *mp;
@@ -2677,32 +2678,29 @@ delitemif_lock_held(PyObject *op, PyObject *key,
26772678

26782679
ASSERT_DICT_LOCKED(op);
26792680

2680-
if (!PyDict_Check(op)) {
2681-
PyErr_BadInternalCall();
2682-
return -1;
2683-
}
26842681
assert(key);
26852682
hash = PyObject_Hash(key);
26862683
if (hash == -1)
26872684
return -1;
26882685
mp = (PyDictObject *)op;
26892686
ix = _Py_dict_lookup(mp, key, hash, &old_value);
2690-
if (ix == DKIX_ERROR)
2687+
if (ix == DKIX_ERROR) {
26912688
return -1;
2689+
}
26922690
if (ix == DKIX_EMPTY || old_value == NULL) {
2693-
_PyErr_SetKeyError(key);
2694-
return -1;
2691+
return 0;
26952692
}
26962693

2697-
res = predicate(old_value);
2694+
res = predicate(old_value, arg);
26982695
if (res == -1)
26992696
return -1;
27002697

27012698
if (res > 0) {
27022699
PyInterpreterState *interp = _PyInterpreterState_GET();
27032700
uint64_t new_version = _PyDict_NotifyEvent(
27042701
interp, PyDict_EVENT_DELETED, mp, key, NULL);
2705-
return delitem_common(mp, hash, ix, old_value, new_version);
2702+
delitem_common(mp, hash, ix, old_value, new_version);
2703+
return 1;
27062704
} else {
27072705
return 0;
27082706
}
@@ -2714,11 +2712,13 @@ delitemif_lock_held(PyObject *op, PyObject *key,
27142712
*/
27152713
int
27162714
_PyDict_DelItemIf(PyObject *op, PyObject *key,
2717-
int (*predicate)(PyObject *value))
2715+
int (*predicate)(PyObject *value, void *arg),
2716+
void *arg)
27182717
{
2718+
assert(PyDict_Check(op));
27192719
int res;
27202720
Py_BEGIN_CRITICAL_SECTION(op);
2721-
res = delitemif_lock_held(op, key, predicate);
2721+
res = delitemif_lock_held(op, key, predicate, arg);
27222722
Py_END_CRITICAL_SECTION();
27232723
return res;
27242724
}

0 commit comments

Comments
 (0)