Skip to content

Commit a15fede

Browse files
authored
pythongh-120974: Make _asyncio._leave_task atomic in the free-threaded build (python#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.
1 parent e6b25e9 commit a15fede

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
@@ -14,8 +14,12 @@ extern "C" {
1414
// Unsafe flavor of PyDict_GetItemWithError(): no error checking
1515
extern PyObject* _PyDict_GetItemWithError(PyObject *dp, PyObject *key);
1616

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

2024
// "KnownHash" variants
2125
// Export for '_asyncio' shared extension

Modules/_asynciomodule.c

+24-18
Original file line numberDiff line numberDiff line change
@@ -1994,30 +1994,36 @@ enter_task(asyncio_state *state, PyObject *loop, PyObject *task)
19941994
return 0;
19951995
}
19961996

1997+
static int
1998+
err_leave_task(PyObject *item, PyObject *task)
1999+
{
2000+
PyErr_Format(
2001+
PyExc_RuntimeError,
2002+
"Leaving task %R does not match the current task %R.",
2003+
task, item);
2004+
return -1;
2005+
}
2006+
2007+
static int
2008+
leave_task_predicate(PyObject *item, void *task)
2009+
{
2010+
if (item != task) {
2011+
return err_leave_task(item, (PyObject *)task);
2012+
}
2013+
return 1;
2014+
}
19972015

19982016
static int
19992017
leave_task(asyncio_state *state, PyObject *loop, PyObject *task)
20002018
/*[clinic end generated code: output=0ebf6db4b858fb41 input=51296a46313d1ad8]*/
20012019
{
2002-
PyObject *item;
2003-
Py_hash_t hash;
2004-
hash = PyObject_Hash(loop);
2005-
if (hash == -1) {
2006-
return -1;
2007-
}
2008-
item = _PyDict_GetItem_KnownHash(state->current_tasks, loop, hash);
2009-
if (item != task) {
2010-
if (item == NULL) {
2011-
/* Not entered, replace with None */
2012-
item = Py_None;
2013-
}
2014-
PyErr_Format(
2015-
PyExc_RuntimeError,
2016-
"Leaving task %R does not match the current task %R.",
2017-
task, item, NULL);
2018-
return -1;
2020+
int res = _PyDict_DelItemIf(state->current_tasks, loop,
2021+
leave_task_predicate, task);
2022+
if (res == 0) {
2023+
// task was not found
2024+
return err_leave_task(Py_None, task);
20192025
}
2020-
return _PyDict_DelItem_KnownHash(state->current_tasks, loop, hash);
2026+
return res;
20212027
}
20222028

20232029
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
@@ -2508,7 +2508,7 @@ delete_index_from_values(PyDictValues *values, Py_ssize_t ix)
25082508
values->size = size;
25092509
}
25102510

2511-
static int
2511+
static void
25122512
delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
25132513
PyObject *old_value, uint64_t new_version)
25142514
{
@@ -2550,7 +2550,6 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
25502550
Py_DECREF(old_value);
25512551

25522552
ASSERT_CONSISTENT(mp);
2553-
return 0;
25542553
}
25552554

25562555
int
@@ -2593,7 +2592,8 @@ delitem_knownhash_lock_held(PyObject *op, PyObject *key, Py_hash_t hash)
25932592
PyInterpreterState *interp = _PyInterpreterState_GET();
25942593
uint64_t new_version = _PyDict_NotifyEvent(
25952594
interp, PyDict_EVENT_DELETED, mp, key, NULL);
2596-
return delitem_common(mp, hash, ix, old_value, new_version);
2595+
delitem_common(mp, hash, ix, old_value, new_version);
2596+
return 0;
25972597
}
25982598

25992599
int
@@ -2608,7 +2608,8 @@ _PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
26082608

26092609
static int
26102610
delitemif_lock_held(PyObject *op, PyObject *key,
2611-
int (*predicate)(PyObject *value))
2611+
int (*predicate)(PyObject *value, void *arg),
2612+
void *arg)
26122613
{
26132614
Py_ssize_t ix;
26142615
PyDictObject *mp;
@@ -2618,32 +2619,29 @@ delitemif_lock_held(PyObject *op, PyObject *key,
26182619

26192620
ASSERT_DICT_LOCKED(op);
26202621

2621-
if (!PyDict_Check(op)) {
2622-
PyErr_BadInternalCall();
2623-
return -1;
2624-
}
26252622
assert(key);
26262623
hash = PyObject_Hash(key);
26272624
if (hash == -1)
26282625
return -1;
26292626
mp = (PyDictObject *)op;
26302627
ix = _Py_dict_lookup(mp, key, hash, &old_value);
2631-
if (ix == DKIX_ERROR)
2628+
if (ix == DKIX_ERROR) {
26322629
return -1;
2630+
}
26332631
if (ix == DKIX_EMPTY || old_value == NULL) {
2634-
_PyErr_SetKeyError(key);
2635-
return -1;
2632+
return 0;
26362633
}
26372634

2638-
res = predicate(old_value);
2635+
res = predicate(old_value, arg);
26392636
if (res == -1)
26402637
return -1;
26412638

26422639
if (res > 0) {
26432640
PyInterpreterState *interp = _PyInterpreterState_GET();
26442641
uint64_t new_version = _PyDict_NotifyEvent(
26452642
interp, PyDict_EVENT_DELETED, mp, key, NULL);
2646-
return delitem_common(mp, hash, ix, old_value, new_version);
2643+
delitem_common(mp, hash, ix, old_value, new_version);
2644+
return 1;
26472645
} else {
26482646
return 0;
26492647
}
@@ -2655,11 +2653,13 @@ delitemif_lock_held(PyObject *op, PyObject *key,
26552653
*/
26562654
int
26572655
_PyDict_DelItemIf(PyObject *op, PyObject *key,
2658-
int (*predicate)(PyObject *value))
2656+
int (*predicate)(PyObject *value, void *arg),
2657+
void *arg)
26592658
{
2659+
assert(PyDict_Check(op));
26602660
int res;
26612661
Py_BEGIN_CRITICAL_SECTION(op);
2662-
res = delitemif_lock_held(op, key, predicate);
2662+
res = delitemif_lock_held(op, key, predicate, arg);
26632663
Py_END_CRITICAL_SECTION();
26642664
return res;
26652665
}

0 commit comments

Comments
 (0)