@@ -1620,6 +1620,12 @@ async_gen_try_set_state(int8_t *state, int8_t *expected, int8_t new_state)
16201620
16211621// Try to transition the async generator to the running state.
16221622// Returns false if it is already running.
1623+ //
1624+ // There are two ways to concurrently iterate an async generator: by
1625+ // sharing a single asend()/athrow() object across threads, or with
1626+ // multiple asend()/athrow() objects sending to the same generator.
1627+ // The CAS on ags_state/agt_state handles the first case; the CAS on
1628+ // ag_running_async here handles the second.
16231629static bool
16241630async_gen_try_claim_running (PyAsyncGenObject * agen )
16251631{
@@ -2026,7 +2032,9 @@ async_gen_asend_send(PyObject *self, PyObject *arg)
20262032 } while (!_Py_ASYNC_GEN_TRY_SET_STATE (o -> ags_state , state ,
20272033 AWAITABLE_STATE_ITER ));
20282034
2029- // INIT -> ITER transition succeeded, this is the first send.
2035+ // The transition above only guards this object, the generator may
2036+ // still be running through another asend()/athrow() object so
2037+ // try to claim it before running.
20302038 if (!async_gen_try_claim_running (o -> ags_gen )) {
20312039 FT_ATOMIC_STORE_INT8_RELAXED (o -> ags_state , AWAITABLE_STATE_CLOSED );
20322040 PyErr_SetString (
@@ -2092,6 +2100,9 @@ async_gen_asend_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
20922100 } while (!_Py_ASYNC_GEN_TRY_SET_STATE (o -> ags_state , state ,
20932101 AWAITABLE_STATE_ITER ));
20942102
2103+ // The transition above only guards this object, the generator may
2104+ // still be running through another asend()/athrow() object so
2105+ // try to claim it before running.
20952106 if (!async_gen_try_claim_running (o -> ags_gen )) {
20962107 FT_ATOMIC_STORE_INT8_RELAXED (o -> ags_state , AWAITABLE_STATE_CLOSED );
20972108 PyErr_SetString (
@@ -2380,7 +2391,9 @@ async_gen_athrow_send(PyObject *self, PyObject *arg)
23802391 } while (!_Py_ASYNC_GEN_TRY_SET_STATE (o -> agt_state , state ,
23812392 AWAITABLE_STATE_ITER ));
23822393
2383- // INIT -> ITER transition succeeded, this is the first send.
2394+ // The transition above only guards this object, the generator may
2395+ // still be running through another asend()/athrow() object so
2396+ // try to claim it before running.
23842397 if (!async_gen_try_claim_running (o -> agt_gen )) {
23852398 FT_ATOMIC_STORE_INT8_RELAXED (o -> agt_state , AWAITABLE_STATE_CLOSED );
23862399 if (o -> agt_typ == NULL ) {
@@ -2506,6 +2519,9 @@ async_gen_athrow_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
25062519 } while (!_Py_ASYNC_GEN_TRY_SET_STATE (o -> agt_state , state ,
25072520 AWAITABLE_STATE_ITER ));
25082521
2522+ // The transition above only guards this object, the generator may
2523+ // still be running through another asend()/athrow() object so
2524+ // try to claim it before running.
25092525 if (!async_gen_try_claim_running (o -> agt_gen )) {
25102526 FT_ATOMIC_STORE_INT8_RELAXED (o -> agt_state , AWAITABLE_STATE_CLOSED );
25112527 if (o -> agt_typ == NULL ) {
0 commit comments