gh-120321: Make gi_yieldfrom thread-safe in free-threading build (#144292)

Add a FRAME_SUSPENDED_YIELD_FROM_LOCKED state that acts as a brief
lock, preventing other threads from transitioning the frame state
while gen_getyieldfrom reads the yield-from object off the stack.
This commit is contained in:
Sam Gross 2026-01-30 12:20:27 -05:00 committed by GitHub
parent a7048327ed
commit a01694dacd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 103 additions and 19 deletions

View file

@ -3391,7 +3391,9 @@ _PyEval_GetAwaitable(PyObject *iterable, int oparg)
else if (PyCoro_CheckExact(iter)) {
PyCoroObject *coro = (PyCoroObject *)iter;
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(coro->cr_frame_state);
if (frame_state == FRAME_SUSPENDED_YIELD_FROM) {
if (frame_state == FRAME_SUSPENDED_YIELD_FROM ||
frame_state == FRAME_SUSPENDED_YIELD_FROM_LOCKED)
{
/* `iter` is a coroutine object that is being awaited. */
Py_CLEAR(iter);
_PyErr_SetString(PyThreadState_GET(), PyExc_RuntimeError,

View file

@ -522,19 +522,22 @@ gen_try_set_executing(PyGenObject *gen)
#ifdef Py_GIL_DISABLED
if (!_PyObject_IsUniquelyReferenced((PyObject *)gen)) {
int8_t frame_state = _Py_atomic_load_int8_relaxed(&gen->gi_frame_state);
while (frame_state < FRAME_EXECUTING) {
while (frame_state < FRAME_SUSPENDED_YIELD_FROM_LOCKED) {
if (_Py_atomic_compare_exchange_int8(&gen->gi_frame_state,
&frame_state,
FRAME_EXECUTING)) {
return true;
}
}
// NB: We return false for FRAME_SUSPENDED_YIELD_FROM_LOCKED as well.
// That case is rare enough that we can just handle it in the deopt.
return false;
}
#endif
// Use faster non-atomic modifications in the GIL-enabled build and when
// the object is uniquely referenced in the free-threaded build.
if (gen->gi_frame_state < FRAME_EXECUTING) {
assert(gen->gi_frame_state != FRAME_SUSPENDED_YIELD_FROM_LOCKED);
gen->gi_frame_state = FRAME_EXECUTING;
return true;
}

View file

@ -40,7 +40,7 @@ struct mutex_entry {
int handed_off;
};
static void
void
_Py_yield(void)
{
#ifdef MS_WINDOWS