Skip to content

Commit b5e6fb3

Browse files
authored
gh-120974: Make asyncio swap_current_task safe in free-threaded build (#122317)
* gh-120974: Make asyncio `swap_current_task` safe in free-threaded build
1 parent fb864c7 commit b5e6fb3

File tree

3 files changed

+67
-31
lines changed

3 files changed

+67
-31
lines changed

Include/internal/pycore_dict.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,13 @@ PyAPI_FUNC(PyObject *)_PyDict_LoadGlobal(PyDictObject *, PyDictObject *, PyObjec
108108
/* Consumes references to key and value */
109109
PyAPI_FUNC(int) _PyDict_SetItem_Take2(PyDictObject *op, PyObject *key, PyObject *value);
110110
extern int _PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject *name, PyObject *value);
111-
extern int _PyDict_GetItemRef_Unicode_LockHeld(PyDictObject *op, PyObject *key, PyObject **result);
111+
// Export for '_asyncio' shared extension
112+
PyAPI_FUNC(int) _PyDict_SetItem_KnownHash_LockHeld(PyDictObject *mp, PyObject *key,
113+
PyObject *value, Py_hash_t hash);
114+
// Export for '_asyncio' shared extension
115+
PyAPI_FUNC(int) _PyDict_GetItemRef_KnownHash_LockHeld(PyDictObject *op, PyObject *key, Py_hash_t hash, PyObject **result);
112116
extern int _PyDict_GetItemRef_KnownHash(PyDictObject *op, PyObject *key, Py_hash_t hash, PyObject **result);
117+
extern int _PyDict_GetItemRef_Unicode_LockHeld(PyDictObject *op, PyObject *key, PyObject **result);
113118
extern int _PyObjectDict_SetItem(PyTypeObject *tp, PyObject *obj, PyObject **dictptr, PyObject *name, PyObject *value);
114119

115120
extern int _PyDict_Pop_KnownHash(

Modules/_asynciomodule.c

+23-14
Original file line numberDiff line numberDiff line change
@@ -2026,6 +2026,24 @@ leave_task(asyncio_state *state, PyObject *loop, PyObject *task)
20262026
return res;
20272027
}
20282028

2029+
static PyObject *
2030+
swap_current_task_lock_held(PyDictObject *current_tasks, PyObject *loop,
2031+
Py_hash_t hash, PyObject *task)
2032+
{
2033+
PyObject *prev_task;
2034+
if (_PyDict_GetItemRef_KnownHash_LockHeld(current_tasks, loop, hash, &prev_task) < 0) {
2035+
return NULL;
2036+
}
2037+
if (_PyDict_SetItem_KnownHash_LockHeld(current_tasks, loop, task, hash) < 0) {
2038+
Py_XDECREF(prev_task);
2039+
return NULL;
2040+
}
2041+
if (prev_task == NULL) {
2042+
Py_RETURN_NONE;
2043+
}
2044+
return prev_task;
2045+
}
2046+
20292047
static PyObject *
20302048
swap_current_task(asyncio_state *state, PyObject *loop, PyObject *task)
20312049
{
@@ -2041,24 +2059,15 @@ swap_current_task(asyncio_state *state, PyObject *loop, PyObject *task)
20412059
return prev_task;
20422060
}
20432061

2044-
Py_hash_t hash;
2045-
hash = PyObject_Hash(loop);
2062+
Py_hash_t hash = PyObject_Hash(loop);
20462063
if (hash == -1) {
20472064
return NULL;
20482065
}
2049-
prev_task = _PyDict_GetItem_KnownHash(state->current_tasks, loop, hash);
2050-
if (prev_task == NULL) {
2051-
if (PyErr_Occurred()) {
2052-
return NULL;
2053-
}
2054-
prev_task = Py_None;
2055-
}
2056-
Py_INCREF(prev_task);
2057-
if (_PyDict_SetItem_KnownHash(state->current_tasks, loop, task, hash) == -1) {
2058-
Py_DECREF(prev_task);
2059-
return NULL;
2060-
}
20612066

2067+
PyDictObject *current_tasks = (PyDictObject *)state->current_tasks;
2068+
Py_BEGIN_CRITICAL_SECTION(current_tasks);
2069+
prev_task = swap_current_task_lock_held(current_tasks, loop, hash, task);
2070+
Py_END_CRITICAL_SECTION();
20622071
return prev_task;
20632072
}
20642073

Objects/dictobject.c

+38-16
Original file line numberDiff line numberDiff line change
@@ -2216,6 +2216,29 @@ _PyDict_GetItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
22162216
return value; // borrowed reference
22172217
}
22182218

2219+
/* Gets an item and provides a new reference if the value is present.
2220+
* Returns 1 if the key is present, 0 if the key is missing, and -1 if an
2221+
* exception occurred.
2222+
*/
2223+
int
2224+
_PyDict_GetItemRef_KnownHash_LockHeld(PyDictObject *op, PyObject *key,
2225+
Py_hash_t hash, PyObject **result)
2226+
{
2227+
PyObject *value;
2228+
Py_ssize_t ix = _Py_dict_lookup(op, key, hash, &value);
2229+
assert(ix >= 0 || value == NULL);
2230+
if (ix == DKIX_ERROR) {
2231+
*result = NULL;
2232+
return -1;
2233+
}
2234+
if (value == NULL) {
2235+
*result = NULL;
2236+
return 0; // missing key
2237+
}
2238+
*result = Py_NewRef(value);
2239+
return 1; // key is present
2240+
}
2241+
22192242
/* Gets an item and provides a new reference if the value is present.
22202243
* Returns 1 if the key is present, 0 if the key is missing, and -1 if an
22212244
* exception occurred.
@@ -2460,33 +2483,32 @@ setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
24602483

24612484

24622485
int
2463-
_PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value,
2464-
Py_hash_t hash)
2486+
_PyDict_SetItem_KnownHash_LockHeld(PyDictObject *mp, PyObject *key, PyObject *value,
2487+
Py_hash_t hash)
24652488
{
2466-
PyDictObject *mp;
2489+
PyInterpreterState *interp = _PyInterpreterState_GET();
2490+
if (mp->ma_keys == Py_EMPTY_KEYS) {
2491+
return insert_to_emptydict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value));
2492+
}
2493+
/* insertdict() handles any resizing that might be necessary */
2494+
return insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value));
2495+
}
24672496

2497+
int
2498+
_PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value,
2499+
Py_hash_t hash)
2500+
{
24682501
if (!PyDict_Check(op)) {
24692502
PyErr_BadInternalCall();
24702503
return -1;
24712504
}
24722505
assert(key);
24732506
assert(value);
24742507
assert(hash != -1);
2475-
mp = (PyDictObject *)op;
24762508

24772509
int res;
2478-
PyInterpreterState *interp = _PyInterpreterState_GET();
2479-
2480-
Py_BEGIN_CRITICAL_SECTION(mp);
2481-
2482-
if (mp->ma_keys == Py_EMPTY_KEYS) {
2483-
res = insert_to_emptydict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value));
2484-
}
2485-
else {
2486-
/* insertdict() handles any resizing that might be necessary */
2487-
res = insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value));
2488-
}
2489-
2510+
Py_BEGIN_CRITICAL_SECTION(op);
2511+
res = _PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)op, key, value, hash);
24902512
Py_END_CRITICAL_SECTION();
24912513
return res;
24922514
}

0 commit comments

Comments
 (0)