mirror of
https://github.com/python/cpython.git
synced 2026-01-08 16:32:55 +00:00
gh-120321: Make gi_frame_state transitions atomic in FT build (gh-142599)
This makes generator frame state transitions atomic in the free threading build, which avoids segfaults when trying to execute a generator from multiple threads concurrently. There are still a few operations that aren't thread-safe and may crash if performed concurrently on the same generator/coroutine: * Accessing gi_yieldfrom/cr_await/ag_await * Accessing gi_frame/cr_frame/ag_frame * Async generator operations
This commit is contained in:
parent
e2a7db7175
commit
08bc03ff2a
16 changed files with 1124 additions and 883 deletions
|
|
@ -523,6 +523,9 @@ _Py_atomic_store_uintptr_release(uintptr_t *obj, uintptr_t value);
|
|||
static inline void
|
||||
_Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value);
|
||||
|
||||
static inline void
|
||||
_Py_atomic_store_int8_release(int8_t *obj, int8_t value);
|
||||
|
||||
static inline void
|
||||
_Py_atomic_store_int_release(int *obj, int value);
|
||||
|
||||
|
|
|
|||
|
|
@ -572,6 +572,10 @@ static inline void
|
|||
_Py_atomic_store_int_release(int *obj, int value)
|
||||
{ __atomic_store_n(obj, value, __ATOMIC_RELEASE); }
|
||||
|
||||
static inline void
|
||||
_Py_atomic_store_int8_release(int8_t *obj, int8_t value)
|
||||
{ __atomic_store_n(obj, value, __ATOMIC_RELEASE); }
|
||||
|
||||
static inline void
|
||||
_Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value)
|
||||
{ __atomic_store_n(obj, value, __ATOMIC_RELEASE); }
|
||||
|
|
|
|||
|
|
@ -1066,6 +1066,19 @@ _Py_atomic_store_int_release(int *obj, int value)
|
|||
#endif
|
||||
}
|
||||
|
||||
static inline void
|
||||
_Py_atomic_store_int8_release(int8_t *obj, int8_t value)
|
||||
{
|
||||
#if defined(_M_X64) || defined(_M_IX86)
|
||||
*(int8_t volatile *)obj = value;
|
||||
#elif defined(_M_ARM64)
|
||||
_Py_atomic_ASSERT_ARG_TYPE(unsigned __int8);
|
||||
__stlr8((unsigned __int8 volatile *)obj, (unsigned __int8)value);
|
||||
#else
|
||||
# error "no implementation of _Py_atomic_store_int8_release"
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void
|
||||
_Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1023,6 +1023,14 @@ _Py_atomic_store_int_release(int *obj, int value)
|
|||
memory_order_release);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_Py_atomic_store_int8_release(int8_t *obj, int8_t value)
|
||||
{
|
||||
_Py_USING_STD;
|
||||
atomic_store_explicit((_Atomic(int8_t)*)obj, value,
|
||||
memory_order_release);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_Py_atomic_store_uint_release(unsigned int *obj, unsigned int value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ extern "C" {
|
|||
_Py_atomic_load_uint8(&value)
|
||||
#define FT_ATOMIC_STORE_UINT8(value, new_value) \
|
||||
_Py_atomic_store_uint8(&value, new_value)
|
||||
#define FT_ATOMIC_LOAD_INT8_RELAXED(value) \
|
||||
_Py_atomic_load_int8_relaxed(&value)
|
||||
#define FT_ATOMIC_LOAD_UINT8_RELAXED(value) \
|
||||
_Py_atomic_load_uint8_relaxed(&value)
|
||||
#define FT_ATOMIC_LOAD_UINT16_RELAXED(value) \
|
||||
|
|
@ -55,6 +57,10 @@ extern "C" {
|
|||
_Py_atomic_store_ptr_release(&value, new_value)
|
||||
#define FT_ATOMIC_STORE_UINTPTR_RELEASE(value, new_value) \
|
||||
_Py_atomic_store_uintptr_release(&value, new_value)
|
||||
#define FT_ATOMIC_STORE_INT8_RELAXED(value, new_value) \
|
||||
_Py_atomic_store_int8_relaxed(&value, new_value)
|
||||
#define FT_ATOMIC_STORE_INT8_RELEASE(value, new_value) \
|
||||
_Py_atomic_store_int8_release(&value, new_value)
|
||||
#define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) \
|
||||
_Py_atomic_store_ssize_relaxed(&value, new_value)
|
||||
#define FT_ATOMIC_STORE_SSIZE_RELEASE(value, new_value) \
|
||||
|
|
@ -134,6 +140,7 @@ extern "C" {
|
|||
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) value
|
||||
#define FT_ATOMIC_LOAD_UINT8(value) value
|
||||
#define FT_ATOMIC_STORE_UINT8(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_LOAD_INT8_RELAXED(value) value
|
||||
#define FT_ATOMIC_LOAD_UINT8_RELAXED(value) value
|
||||
#define FT_ATOMIC_LOAD_UINT16_RELAXED(value) value
|
||||
#define FT_ATOMIC_LOAD_UINT32_RELAXED(value) value
|
||||
|
|
@ -141,6 +148,8 @@ extern "C" {
|
|||
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_STORE_UINTPTR_RELEASE(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_STORE_INT8_RELAXED(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_STORE_INT8_RELEASE(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_STORE_SSIZE_RELEASE(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_STORE_UINT8_RELAXED(value, new_value) value = new_value
|
||||
|
|
|
|||
|
|
@ -82,6 +82,13 @@ typedef struct _PyThreadStateImpl {
|
|||
PyObject *asyncio_running_loop; // Strong reference
|
||||
PyObject *asyncio_running_task; // Strong reference
|
||||
|
||||
// Distinguishes between yield and return from PyEval_EvalFrame().
|
||||
// See gen_send_ex2() in Objects/genobject.c
|
||||
enum {
|
||||
GENERATOR_RETURN = 0,
|
||||
GENERATOR_YIELD = 1,
|
||||
} generator_return_kind;
|
||||
|
||||
/* Head of circular linked-list of all tasks which are instances of `asyncio.Task`
|
||||
or subclasses of it used in `asyncio.all_tasks`.
|
||||
*/
|
||||
|
|
|
|||
1134
Include/internal/pycore_uop_ids.h
generated
1134
Include/internal/pycore_uop_ids.h
generated
File diff suppressed because it is too large
Load diff
10
Include/internal/pycore_uop_metadata.h
generated
10
Include/internal/pycore_uop_metadata.h
generated
|
|
@ -2131,10 +2131,10 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = {
|
|||
},
|
||||
},
|
||||
[_FOR_ITER_GEN_FRAME] = {
|
||||
.best = { 2, 2, 2, 2 },
|
||||
.best = { 0, 1, 2, 2 },
|
||||
.entries = {
|
||||
{ -1, -1, -1 },
|
||||
{ -1, -1, -1 },
|
||||
{ 3, 0, _FOR_ITER_GEN_FRAME_r03 },
|
||||
{ 3, 1, _FOR_ITER_GEN_FRAME_r13 },
|
||||
{ 3, 2, _FOR_ITER_GEN_FRAME_r23 },
|
||||
{ -1, -1, -1 },
|
||||
},
|
||||
|
|
@ -3620,6 +3620,8 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = {
|
|||
[_ITER_NEXT_RANGE_r03] = _ITER_NEXT_RANGE,
|
||||
[_ITER_NEXT_RANGE_r13] = _ITER_NEXT_RANGE,
|
||||
[_ITER_NEXT_RANGE_r23] = _ITER_NEXT_RANGE,
|
||||
[_FOR_ITER_GEN_FRAME_r03] = _FOR_ITER_GEN_FRAME,
|
||||
[_FOR_ITER_GEN_FRAME_r13] = _FOR_ITER_GEN_FRAME,
|
||||
[_FOR_ITER_GEN_FRAME_r23] = _FOR_ITER_GEN_FRAME,
|
||||
[_INSERT_NULL_r10] = _INSERT_NULL,
|
||||
[_LOAD_SPECIAL_r00] = _LOAD_SPECIAL,
|
||||
|
|
@ -4182,6 +4184,8 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = {
|
|||
[_FORMAT_WITH_SPEC] = "_FORMAT_WITH_SPEC",
|
||||
[_FORMAT_WITH_SPEC_r21] = "_FORMAT_WITH_SPEC_r21",
|
||||
[_FOR_ITER_GEN_FRAME] = "_FOR_ITER_GEN_FRAME",
|
||||
[_FOR_ITER_GEN_FRAME_r03] = "_FOR_ITER_GEN_FRAME_r03",
|
||||
[_FOR_ITER_GEN_FRAME_r13] = "_FOR_ITER_GEN_FRAME_r13",
|
||||
[_FOR_ITER_GEN_FRAME_r23] = "_FOR_ITER_GEN_FRAME_r23",
|
||||
[_FOR_ITER_TIER_TWO] = "_FOR_ITER_TIER_TWO",
|
||||
[_FOR_ITER_TIER_TWO_r23] = "_FOR_ITER_TIER_TWO_r23",
|
||||
|
|
|
|||
|
|
@ -49,3 +49,74 @@ def test_concurrent_write(self):
|
|||
self.concurrent_write_with_func(func=set_gen_name)
|
||||
with self.subTest(func=set_gen_qualname):
|
||||
self.concurrent_write_with_func(func=set_gen_qualname)
|
||||
|
||||
def test_concurrent_send(self):
|
||||
def gen():
|
||||
yield 1
|
||||
yield 2
|
||||
yield 3
|
||||
yield 4
|
||||
yield 5
|
||||
|
||||
def run_test(drive_generator):
|
||||
g = gen()
|
||||
values = []
|
||||
threading_helper.run_concurrently(drive_generator, self.NUM_THREADS, args=(g, values,))
|
||||
self.assertEqual(sorted(values), [1, 2, 3, 4, 5])
|
||||
|
||||
def call_next(g, values):
|
||||
while True:
|
||||
try:
|
||||
values.append(next(g))
|
||||
except ValueError:
|
||||
continue
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
with self.subTest(method='next'):
|
||||
run_test(call_next)
|
||||
|
||||
def call_send(g, values):
|
||||
while True:
|
||||
try:
|
||||
values.append(g.send(None))
|
||||
except ValueError:
|
||||
continue
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
with self.subTest(method='send'):
|
||||
run_test(call_send)
|
||||
|
||||
def for_iter_gen(g, values):
|
||||
while True:
|
||||
try:
|
||||
for value in g:
|
||||
values.append(value)
|
||||
else:
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
with self.subTest(method='for'):
|
||||
run_test(for_iter_gen)
|
||||
|
||||
def test_concurrent_close(self):
|
||||
def gen():
|
||||
for i in range(10):
|
||||
yield i
|
||||
time.sleep(0.001)
|
||||
|
||||
def drive_generator(g):
|
||||
while True:
|
||||
try:
|
||||
for value in g:
|
||||
if value == 5:
|
||||
g.close()
|
||||
else:
|
||||
return
|
||||
except ValueError as e:
|
||||
self.assertEqual(e.args[0], "generator already executing")
|
||||
|
||||
g = gen()
|
||||
threading_helper.run_concurrently(drive_generator, self.NUM_THREADS, args=(g,))
|
||||
|
|
|
|||
|
|
@ -36,6 +36,14 @@ static PyObject* async_gen_athrow_new(PyAsyncGenObject *, PyObject *);
|
|||
#define _PyAsyncGenObject_CAST(op) \
|
||||
_Py_CAST(PyAsyncGenObject*, (op))
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
# define _Py_GEN_TRY_SET_FRAME_STATE(gen, expected, state) \
|
||||
_Py_atomic_compare_exchange_int8(&(gen)->gi_frame_state, &expected, (state))
|
||||
#else
|
||||
# define _Py_GEN_TRY_SET_FRAME_STATE(gen, expected, state) \
|
||||
((gen)->gi_frame_state = (state), true)
|
||||
#endif
|
||||
|
||||
|
||||
static const char *NON_INIT_CORO_MSG = "can't send non-None value to a "
|
||||
"just-started coroutine";
|
||||
|
|
@ -145,10 +153,7 @@ _PyGen_Finalize(PyObject *self)
|
|||
static void
|
||||
gen_clear_frame(PyGenObject *gen)
|
||||
{
|
||||
if (gen->gi_frame_state == FRAME_CLEARED)
|
||||
return;
|
||||
|
||||
gen->gi_frame_state = FRAME_CLEARED;
|
||||
assert(gen->gi_frame_state == FRAME_CLEARED);
|
||||
_PyInterpreterFrame *frame = &gen->gi_iframe;
|
||||
frame->previous = NULL;
|
||||
_PyFrame_ClearExceptCode(frame);
|
||||
|
|
@ -179,7 +184,10 @@ gen_dealloc(PyObject *self)
|
|||
if (PyCoro_CheckExact(gen)) {
|
||||
Py_CLEAR(((PyCoroObject *)gen)->cr_origin_or_finalizer);
|
||||
}
|
||||
gen_clear_frame(gen);
|
||||
if (gen->gi_frame_state != FRAME_CLEARED) {
|
||||
gen->gi_frame_state = FRAME_CLEARED;
|
||||
gen_clear_frame(gen);
|
||||
}
|
||||
assert(gen->gi_exc_state.exc_value == NULL);
|
||||
PyStackRef_CLEAR(gen->gi_iframe.f_executable);
|
||||
Py_CLEAR(gen->gi_name);
|
||||
|
|
@ -188,59 +196,32 @@ gen_dealloc(PyObject *self)
|
|||
PyObject_GC_Del(gen);
|
||||
}
|
||||
|
||||
static PySendResult
|
||||
gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
|
||||
int exc, int closing)
|
||||
static void
|
||||
gen_raise_already_executing_error(PyGenObject *gen)
|
||||
{
|
||||
const char *msg = "generator already executing";
|
||||
if (PyCoro_CheckExact(gen)) {
|
||||
msg = "coroutine already executing";
|
||||
}
|
||||
else if (PyAsyncGen_CheckExact(gen)) {
|
||||
msg = "async generator already executing";
|
||||
}
|
||||
PyErr_SetString(PyExc_ValueError, msg);
|
||||
}
|
||||
|
||||
// Send 'arg' into 'gen'. On success, return PYGEN_NEXT or PYGEN_RETURN.
|
||||
// Returns PYGEN_ERROR on failure. 'presult' is set to the yielded or
|
||||
// returned value.
|
||||
// The generator must be in the FRAME_EXECUTING state when this function
|
||||
// is called.
|
||||
static PySendResult
|
||||
gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult, int exc)
|
||||
{
|
||||
assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_EXECUTING);
|
||||
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
_PyInterpreterFrame *frame = &gen->gi_iframe;
|
||||
|
||||
*presult = NULL;
|
||||
if (gen->gi_frame_state == FRAME_CREATED && arg && arg != Py_None) {
|
||||
const char *msg = "can't send non-None value to a "
|
||||
"just-started generator";
|
||||
if (PyCoro_CheckExact(gen)) {
|
||||
msg = NON_INIT_CORO_MSG;
|
||||
}
|
||||
else if (PyAsyncGen_CheckExact(gen)) {
|
||||
msg = "can't send non-None value to a "
|
||||
"just-started async generator";
|
||||
}
|
||||
PyErr_SetString(PyExc_TypeError, msg);
|
||||
return PYGEN_ERROR;
|
||||
}
|
||||
if (gen->gi_frame_state == FRAME_EXECUTING) {
|
||||
const char *msg = "generator already executing";
|
||||
if (PyCoro_CheckExact(gen)) {
|
||||
msg = "coroutine already executing";
|
||||
}
|
||||
else if (PyAsyncGen_CheckExact(gen)) {
|
||||
msg = "async generator already executing";
|
||||
}
|
||||
PyErr_SetString(PyExc_ValueError, msg);
|
||||
return PYGEN_ERROR;
|
||||
}
|
||||
if (FRAME_STATE_FINISHED(gen->gi_frame_state)) {
|
||||
if (PyCoro_CheckExact(gen) && !closing) {
|
||||
/* `gen` is an exhausted coroutine: raise an error,
|
||||
except when called from gen_close(), which should
|
||||
always be a silent method. */
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError,
|
||||
"cannot reuse already awaited coroutine");
|
||||
}
|
||||
else if (arg && !exc) {
|
||||
/* `gen` is an exhausted generator:
|
||||
only return value if called from send(). */
|
||||
*presult = Py_NewRef(Py_None);
|
||||
return PYGEN_RETURN;
|
||||
}
|
||||
return PYGEN_ERROR;
|
||||
}
|
||||
|
||||
assert((gen->gi_frame_state == FRAME_CREATED) ||
|
||||
FRAME_STATE_SUSPENDED(gen->gi_frame_state));
|
||||
|
||||
/* Push arg onto the frame's value stack */
|
||||
PyObject *arg_obj = arg ? arg : Py_None;
|
||||
_PyFrame_StackPush(frame, PyStackRef_FromPyObjectNew(arg_obj));
|
||||
|
|
@ -254,21 +235,34 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
|
|||
_PyErr_ChainStackItem();
|
||||
}
|
||||
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
EVAL_CALL_STAT_INC(EVAL_CALL_GENERATOR);
|
||||
PyObject *result = _PyEval_EvalFrame(tstate, frame, exc);
|
||||
assert(tstate->exc_info == prev_exc_info);
|
||||
#ifndef Py_GIL_DISABLED
|
||||
assert(gen->gi_exc_state.previous_item == NULL);
|
||||
assert(gen->gi_frame_state != FRAME_EXECUTING);
|
||||
assert(frame->previous == NULL);
|
||||
assert(gen->gi_frame_state != FRAME_EXECUTING);
|
||||
#endif
|
||||
|
||||
// The generator_return_kind field is used to distinguish between a
|
||||
// yield and a return from within _PyEval_EvalFrame(). Earlier versions
|
||||
// of CPython (prior to 3.15) used gi_frame_state for this purpose, but
|
||||
// that requires the GIL for thread-safety.
|
||||
int return_kind = ((_PyThreadStateImpl *)tstate)->generator_return_kind;
|
||||
|
||||
if (return_kind == GENERATOR_YIELD) {
|
||||
assert(result != NULL && !_PyErr_Occurred(tstate));
|
||||
*presult = result;
|
||||
return PYGEN_NEXT;
|
||||
}
|
||||
|
||||
assert(return_kind == GENERATOR_RETURN);
|
||||
assert(gen->gi_exc_state.exc_value == NULL);
|
||||
assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_CLEARED);
|
||||
|
||||
/* If the generator just returned (as opposed to yielding), signal
|
||||
* that the generator is exhausted. */
|
||||
if (result) {
|
||||
if (FRAME_STATE_SUSPENDED(gen->gi_frame_state)) {
|
||||
*presult = result;
|
||||
return PYGEN_NEXT;
|
||||
}
|
||||
assert(result == Py_None || !PyAsyncGen_CheckExact(gen));
|
||||
if (result == Py_None && !PyAsyncGen_CheckExact(gen) && !arg) {
|
||||
/* Return NULL if called by gen_iternext() */
|
||||
|
|
@ -281,37 +275,82 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
|
|||
!PyErr_ExceptionMatches(PyExc_StopAsyncIteration));
|
||||
}
|
||||
|
||||
assert(gen->gi_exc_state.exc_value == NULL);
|
||||
assert(gen->gi_frame_state == FRAME_CLEARED);
|
||||
*presult = result;
|
||||
return result ? PYGEN_RETURN : PYGEN_ERROR;
|
||||
}
|
||||
|
||||
// Set the generator 'gen' to the executing state and send 'arg' into it.
|
||||
// See gen_send_ex2() for details.
|
||||
static PySendResult
|
||||
gen_send_ex(PyGenObject *gen, PyObject *arg, PyObject **presult)
|
||||
{
|
||||
*presult = NULL;
|
||||
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
|
||||
do {
|
||||
if (frame_state == FRAME_CREATED && arg && arg != Py_None) {
|
||||
const char *msg = "can't send non-None value to a "
|
||||
"just-started generator";
|
||||
if (PyCoro_CheckExact(gen)) {
|
||||
msg = NON_INIT_CORO_MSG;
|
||||
}
|
||||
else if (PyAsyncGen_CheckExact(gen)) {
|
||||
msg = "can't send non-None value to a "
|
||||
"just-started async generator";
|
||||
}
|
||||
PyErr_SetString(PyExc_TypeError, msg);
|
||||
return PYGEN_ERROR;
|
||||
}
|
||||
if (frame_state == FRAME_EXECUTING) {
|
||||
gen_raise_already_executing_error(gen);
|
||||
return PYGEN_ERROR;
|
||||
}
|
||||
if (FRAME_STATE_FINISHED(frame_state)) {
|
||||
if (PyCoro_CheckExact(gen)) {
|
||||
/* `gen` is an exhausted coroutine: raise an error,
|
||||
except when called from gen_close(), which should
|
||||
always be a silent method. */
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError,
|
||||
"cannot reuse already awaited coroutine");
|
||||
}
|
||||
else if (arg) {
|
||||
/* `gen` is an exhausted generator:
|
||||
only return value if called from send(). */
|
||||
*presult = Py_None;
|
||||
return PYGEN_RETURN;
|
||||
}
|
||||
return PYGEN_ERROR;
|
||||
}
|
||||
|
||||
assert((frame_state == FRAME_CREATED) ||
|
||||
FRAME_STATE_SUSPENDED(frame_state));
|
||||
} while (!_Py_GEN_TRY_SET_FRAME_STATE(gen, frame_state, FRAME_EXECUTING));
|
||||
|
||||
return gen_send_ex2(gen, arg, presult, 0);
|
||||
}
|
||||
|
||||
static PySendResult
|
||||
PyGen_am_send(PyObject *self, PyObject *arg, PyObject **result)
|
||||
{
|
||||
PyGenObject *gen = _PyGen_CAST(self);
|
||||
return gen_send_ex2(gen, arg, result, 0, 0);
|
||||
return gen_send_ex(gen, arg, result);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
|
||||
gen_set_stop_iteration(PyGenObject *gen, PyObject *result)
|
||||
{
|
||||
PyObject *result;
|
||||
if (gen_send_ex2(gen, arg, &result, exc, closing) == PYGEN_RETURN) {
|
||||
if (PyAsyncGen_CheckExact(gen)) {
|
||||
assert(result == Py_None);
|
||||
PyErr_SetNone(PyExc_StopAsyncIteration);
|
||||
}
|
||||
else if (result == Py_None) {
|
||||
PyErr_SetNone(PyExc_StopIteration);
|
||||
}
|
||||
else {
|
||||
_PyGen_SetStopIterationValue(result);
|
||||
}
|
||||
Py_CLEAR(result);
|
||||
if (PyAsyncGen_CheckExact(gen)) {
|
||||
assert(result == Py_None);
|
||||
PyErr_SetNone(PyExc_StopAsyncIteration);
|
||||
}
|
||||
return result;
|
||||
else if (result == Py_None) {
|
||||
PyErr_SetNone(PyExc_StopIteration);
|
||||
}
|
||||
else {
|
||||
_PyGen_SetStopIterationValue(result);
|
||||
}
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(send_doc,
|
||||
|
|
@ -319,9 +358,14 @@ PyDoc_STRVAR(send_doc,
|
|||
return next yielded value or raise StopIteration.");
|
||||
|
||||
static PyObject *
|
||||
gen_send(PyObject *gen, PyObject *arg)
|
||||
gen_send(PyObject *op, PyObject *arg)
|
||||
{
|
||||
return gen_send_ex((PyGenObject*)gen, arg, 0, 0);
|
||||
PyObject *result;
|
||||
PyGenObject *gen = _PyGen_CAST(op);
|
||||
if (gen_send_ex(gen, arg, &result) == PYGEN_RETURN) {
|
||||
return gen_set_stop_iteration(gen, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(close_doc,
|
||||
|
|
@ -370,43 +414,43 @@ is_resume(_Py_CODEUNIT *instr)
|
|||
);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
_PyGen_yf(PyGenObject *gen)
|
||||
{
|
||||
if (gen->gi_frame_state == FRAME_SUSPENDED_YIELD_FROM) {
|
||||
_PyInterpreterFrame *frame = &gen->gi_iframe;
|
||||
// GH-122390: These asserts are wrong in the presence of ENTER_EXECUTOR!
|
||||
// assert(is_resume(frame->instr_ptr));
|
||||
// assert((frame->instr_ptr->op.arg & RESUME_OPARG_LOCATION_MASK) >= RESUME_AFTER_YIELD_FROM);
|
||||
return PyStackRef_AsPyObjectNew(_PyFrame_StackPeek(frame));
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
gen_close(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyGenObject *gen = _PyGen_CAST(self);
|
||||
|
||||
if (gen->gi_frame_state == FRAME_CREATED) {
|
||||
gen->gi_frame_state = FRAME_COMPLETED;
|
||||
gen_clear_frame(gen);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
if (FRAME_STATE_FINISHED(gen->gi_frame_state)) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
|
||||
do {
|
||||
if (frame_state == FRAME_CREATED) {
|
||||
if (!_Py_GEN_TRY_SET_FRAME_STATE(gen, frame_state, FRAME_CLEARED)) {
|
||||
continue;
|
||||
}
|
||||
gen_clear_frame(gen);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
if (FRAME_STATE_FINISHED(frame_state)) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
if (frame_state == FRAME_EXECUTING) {
|
||||
gen_raise_already_executing_error(gen);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(frame_state == FRAME_SUSPENDED_YIELD_FROM ||
|
||||
frame_state == FRAME_SUSPENDED);
|
||||
|
||||
} while (!_Py_GEN_TRY_SET_FRAME_STATE(gen, frame_state, FRAME_EXECUTING));
|
||||
|
||||
PyObject *yf = _PyGen_yf(gen);
|
||||
int err = 0;
|
||||
if (yf) {
|
||||
PyFrameState state = gen->gi_frame_state;
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
_PyInterpreterFrame *frame = &gen->gi_iframe;
|
||||
if (frame_state == FRAME_SUSPENDED_YIELD_FROM) {
|
||||
PyObject *yf = PyStackRef_AsPyObjectNew(_PyFrame_StackPeek(frame));
|
||||
err = gen_close_iter(yf);
|
||||
gen->gi_frame_state = state;
|
||||
Py_DECREF(yf);
|
||||
}
|
||||
_PyInterpreterFrame *frame = &gen->gi_iframe;
|
||||
|
||||
if (is_resume(frame->instr_ptr)) {
|
||||
bool no_unwind_tools = _PyEval_NoToolsForUnwind(_PyThreadState_GET());
|
||||
/* We can safely ignore the outermost try block
|
||||
|
|
@ -416,7 +460,7 @@ gen_close(PyObject *self, PyObject *args)
|
|||
if (oparg & RESUME_OPARG_DEPTH1_MASK && no_unwind_tools) {
|
||||
// RESUME after YIELD_VALUE and exception depth is 1
|
||||
assert((oparg & RESUME_OPARG_LOCATION_MASK) != RESUME_AT_FUNC_START);
|
||||
gen->gi_frame_state = FRAME_COMPLETED;
|
||||
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_CLEARED);
|
||||
gen_clear_frame(gen);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
|
@ -425,8 +469,13 @@ gen_close(PyObject *self, PyObject *args)
|
|||
PyErr_SetNone(PyExc_GeneratorExit);
|
||||
}
|
||||
|
||||
PyObject *retval = gen_send_ex(gen, Py_None, 1, 1);
|
||||
if (retval) {
|
||||
PyObject *retval;
|
||||
if (gen_send_ex2(gen, Py_None, &retval, 1) == PYGEN_RETURN) {
|
||||
// the generator returned a value while closing, return the value here
|
||||
assert(!PyErr_Occurred());
|
||||
return retval;
|
||||
}
|
||||
else if (retval) {
|
||||
const char *msg = "generator ignored GeneratorExit";
|
||||
if (PyCoro_CheckExact(gen)) {
|
||||
msg = "coroutine ignored GeneratorExit";
|
||||
|
|
@ -443,102 +492,14 @@ gen_close(PyObject *self, PyObject *args)
|
|||
PyErr_Clear(); /* ignore this error */
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/* if the generator returned a value while closing, StopIteration was
|
||||
* raised in gen_send_ex() above; retrieve and return the value here */
|
||||
if (_PyGen_FetchStopIterationValue(&retval) == 0) {
|
||||
return retval;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
PyDoc_STRVAR(throw_doc,
|
||||
"throw(value)\n\
|
||||
throw(type[,value[,tb]])\n\
|
||||
\n\
|
||||
Raise exception in generator, return next yielded value or raise\n\
|
||||
StopIteration.\n\
|
||||
the (type, val, tb) signature is deprecated, \n\
|
||||
and may be removed in a future version of Python.");
|
||||
|
||||
static PyObject *
|
||||
_gen_throw(PyGenObject *gen, int close_on_genexit,
|
||||
PyObject *typ, PyObject *val, PyObject *tb)
|
||||
// Set an exception for a gen.throw() call.
|
||||
// Return 0 on success, -1 on failure.
|
||||
static int
|
||||
gen_set_exception(PyObject *typ, PyObject *val, PyObject *tb)
|
||||
{
|
||||
PyObject *yf = _PyGen_yf(gen);
|
||||
|
||||
if (yf) {
|
||||
_PyInterpreterFrame *frame = &gen->gi_iframe;
|
||||
PyObject *ret;
|
||||
int err;
|
||||
if (PyErr_GivenExceptionMatches(typ, PyExc_GeneratorExit) &&
|
||||
close_on_genexit
|
||||
) {
|
||||
/* Asynchronous generators *should not* be closed right away.
|
||||
We have to allow some awaits to work it through, hence the
|
||||
`close_on_genexit` parameter here.
|
||||
*/
|
||||
PyFrameState state = gen->gi_frame_state;
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
err = gen_close_iter(yf);
|
||||
gen->gi_frame_state = state;
|
||||
Py_DECREF(yf);
|
||||
if (err < 0)
|
||||
return gen_send_ex(gen, Py_None, 1, 0);
|
||||
goto throw_here;
|
||||
}
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
assert(tstate != NULL);
|
||||
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
|
||||
/* `yf` is a generator or a coroutine. */
|
||||
|
||||
/* Link frame into the stack to enable complete backtraces. */
|
||||
/* XXX We should probably be updating the current frame somewhere in
|
||||
ceval.c. */
|
||||
_PyInterpreterFrame *prev = tstate->current_frame;
|
||||
frame->previous = prev;
|
||||
tstate->current_frame = frame;
|
||||
/* Close the generator that we are currently iterating with
|
||||
'yield from' or awaiting on with 'await'. */
|
||||
PyFrameState state = gen->gi_frame_state;
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
|
||||
typ, val, tb);
|
||||
gen->gi_frame_state = state;
|
||||
tstate->current_frame = prev;
|
||||
frame->previous = NULL;
|
||||
} else {
|
||||
/* `yf` is an iterator or a coroutine-like object. */
|
||||
PyObject *meth;
|
||||
if (PyObject_GetOptionalAttr(yf, &_Py_ID(throw), &meth) < 0) {
|
||||
Py_DECREF(yf);
|
||||
return NULL;
|
||||
}
|
||||
if (meth == NULL) {
|
||||
Py_DECREF(yf);
|
||||
goto throw_here;
|
||||
}
|
||||
|
||||
_PyInterpreterFrame *prev = tstate->current_frame;
|
||||
frame->previous = prev;
|
||||
tstate->current_frame = frame;
|
||||
PyFrameState state = gen->gi_frame_state;
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
|
||||
gen->gi_frame_state = state;
|
||||
tstate->current_frame = prev;
|
||||
frame->previous = NULL;
|
||||
Py_DECREF(meth);
|
||||
}
|
||||
Py_DECREF(yf);
|
||||
if (!ret) {
|
||||
ret = gen_send_ex(gen, Py_None, 1, 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
throw_here:
|
||||
/* First, check the traceback argument, replacing None with
|
||||
NULL. */
|
||||
if (tb == Py_None) {
|
||||
|
|
@ -547,16 +508,16 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
|
|||
else if (tb != NULL && !PyTraceBack_Check(tb)) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"throw() third argument must be a traceback object");
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_INCREF(typ);
|
||||
Py_XINCREF(val);
|
||||
Py_XINCREF(tb);
|
||||
|
||||
if (PyExceptionClass_Check(typ))
|
||||
if (PyExceptionClass_Check(typ)) {
|
||||
PyErr_NormalizeException(&typ, &val, &tb);
|
||||
|
||||
}
|
||||
else if (PyExceptionInstance_Check(typ)) {
|
||||
/* Raising an instance. The value should be a dummy. */
|
||||
if (val && val != Py_None) {
|
||||
|
|
@ -584,14 +545,137 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
|
|||
}
|
||||
|
||||
PyErr_Restore(typ, val, tb);
|
||||
return gen_send_ex(gen, Py_None, 1, 0);
|
||||
return 0;
|
||||
|
||||
failed_throw:
|
||||
/* Didn't use our arguments, so restore their original refcounts */
|
||||
Py_DECREF(typ);
|
||||
Py_XDECREF(val);
|
||||
Py_XDECREF(tb);
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
gen_throw_current_exception(PyGenObject *gen)
|
||||
{
|
||||
assert(gen->gi_frame_state == FRAME_EXECUTING);
|
||||
|
||||
PyObject *result;
|
||||
if (gen_send_ex2(gen, Py_None, &result, 1) == PYGEN_RETURN) {
|
||||
return gen_set_stop_iteration(gen, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(throw_doc,
|
||||
"throw(value)\n\
|
||||
throw(type[,value[,tb]])\n\
|
||||
\n\
|
||||
Raise exception in generator, return next yielded value or raise\n\
|
||||
StopIteration.\n\
|
||||
the (type, val, tb) signature is deprecated, \n\
|
||||
and may be removed in a future version of Python.");
|
||||
|
||||
static PyObject *
|
||||
_gen_throw(PyGenObject *gen, int close_on_genexit,
|
||||
PyObject *typ, PyObject *val, PyObject *tb)
|
||||
{
|
||||
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
|
||||
do {
|
||||
if (frame_state == FRAME_EXECUTING) {
|
||||
gen_raise_already_executing_error(gen);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (FRAME_STATE_FINISHED(frame_state)) {
|
||||
if (PyCoro_CheckExact(gen)) {
|
||||
/* `gen` is an exhausted coroutine: raise an error */
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError,
|
||||
"cannot reuse already awaited coroutine");
|
||||
return NULL;
|
||||
}
|
||||
gen_set_exception(typ, val, tb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert((frame_state == FRAME_CREATED) ||
|
||||
FRAME_STATE_SUSPENDED(frame_state));
|
||||
} while (!_Py_GEN_TRY_SET_FRAME_STATE(gen, frame_state, FRAME_EXECUTING));
|
||||
|
||||
if (frame_state == FRAME_SUSPENDED_YIELD_FROM) {
|
||||
_PyInterpreterFrame *frame = &gen->gi_iframe;
|
||||
PyObject *yf = PyStackRef_AsPyObjectNew(_PyFrame_StackPeek(frame));
|
||||
PyObject *ret;
|
||||
int err;
|
||||
if (PyErr_GivenExceptionMatches(typ, PyExc_GeneratorExit) &&
|
||||
close_on_genexit
|
||||
) {
|
||||
/* Asynchronous generators *should not* be closed right away.
|
||||
We have to allow some awaits to work it through, hence the
|
||||
`close_on_genexit` parameter here.
|
||||
*/
|
||||
err = gen_close_iter(yf);
|
||||
Py_DECREF(yf);
|
||||
if (err < 0) {
|
||||
return gen_throw_current_exception(gen);
|
||||
}
|
||||
goto throw_here;
|
||||
}
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
assert(tstate != NULL);
|
||||
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
|
||||
/* `yf` is a generator or a coroutine. */
|
||||
|
||||
/* Link frame into the stack to enable complete backtraces. */
|
||||
/* XXX We should probably be updating the current frame somewhere in
|
||||
ceval.c. */
|
||||
_PyInterpreterFrame *prev = tstate->current_frame;
|
||||
frame->previous = prev;
|
||||
tstate->current_frame = frame;
|
||||
/* Close the generator that we are currently iterating with
|
||||
'yield from' or awaiting on with 'await'. */
|
||||
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
|
||||
typ, val, tb);
|
||||
tstate->current_frame = prev;
|
||||
frame->previous = NULL;
|
||||
}
|
||||
else {
|
||||
/* `yf` is an iterator or a coroutine-like object. */
|
||||
PyObject *meth;
|
||||
if (PyObject_GetOptionalAttr(yf, &_Py_ID(throw), &meth) < 0) {
|
||||
Py_DECREF(yf);
|
||||
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, frame_state);
|
||||
return NULL;
|
||||
}
|
||||
if (meth == NULL) {
|
||||
Py_DECREF(yf);
|
||||
goto throw_here;
|
||||
}
|
||||
|
||||
_PyInterpreterFrame *prev = tstate->current_frame;
|
||||
frame->previous = prev;
|
||||
tstate->current_frame = frame;
|
||||
ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
|
||||
tstate->current_frame = prev;
|
||||
frame->previous = NULL;
|
||||
Py_DECREF(meth);
|
||||
}
|
||||
Py_DECREF(yf);
|
||||
if (!ret) {
|
||||
return gen_throw_current_exception(gen);
|
||||
}
|
||||
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, frame_state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
throw_here:
|
||||
assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_EXECUTING);
|
||||
if (gen_set_exception(typ, val, tb) < 0) {
|
||||
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, frame_state);
|
||||
return NULL;
|
||||
}
|
||||
return gen_throw_current_exception(gen);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -633,7 +717,7 @@ gen_iternext(PyObject *self)
|
|||
PyGenObject *gen = _PyGen_CAST(self);
|
||||
|
||||
PyObject *result;
|
||||
if (gen_send_ex2(gen, NULL, &result, 0, 0) == PYGEN_RETURN) {
|
||||
if (gen_send_ex(gen, NULL, &result) == PYGEN_RETURN) {
|
||||
if (result != Py_None) {
|
||||
_PyGen_SetStopIterationValue(result);
|
||||
}
|
||||
|
|
@ -757,13 +841,15 @@ gen_set_qualname(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
|
|||
}
|
||||
|
||||
static PyObject *
|
||||
gen_getyieldfrom(PyObject *gen, void *Py_UNUSED(ignored))
|
||||
gen_getyieldfrom(PyObject *self, void *Py_UNUSED(ignored))
|
||||
{
|
||||
PyObject *yf = _PyGen_yf(_PyGen_CAST(gen));
|
||||
if (yf == NULL) {
|
||||
PyGenObject *gen = _PyGen_CAST(self);
|
||||
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
|
||||
if (frame_state != FRAME_SUSPENDED_YIELD_FROM) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return yf;
|
||||
// TODO: still not thread-safe with free threading
|
||||
return PyStackRef_AsPyObjectNew(_PyFrame_StackPeek(&gen->gi_iframe));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -771,17 +857,16 @@ static PyObject *
|
|||
gen_getrunning(PyObject *self, void *Py_UNUSED(ignored))
|
||||
{
|
||||
PyGenObject *gen = _PyGen_CAST(self);
|
||||
if (gen->gi_frame_state == FRAME_EXECUTING) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
|
||||
return frame_state == FRAME_EXECUTING ? Py_True : Py_False;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
gen_getsuspended(PyObject *self, void *Py_UNUSED(ignored))
|
||||
{
|
||||
PyGenObject *gen = _PyGen_CAST(self);
|
||||
return PyBool_FromLong(FRAME_STATE_SUSPENDED(gen->gi_frame_state));
|
||||
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
|
||||
return FRAME_STATE_SUSPENDED(frame_state) ? Py_True : Py_False;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
|
|
@ -790,9 +875,11 @@ _gen_getframe(PyGenObject *gen, const char *const name)
|
|||
if (PySys_Audit("object.__getattr__", "Os", gen, name) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (FRAME_STATE_FINISHED(gen->gi_frame_state)) {
|
||||
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
|
||||
if (FRAME_STATE_FINISHED(frame_state)) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
// TODO: still not thread-safe with free threading
|
||||
return _Py_XNewRef((PyObject *)_PyFrame_GetFrameObject(&gen->gi_iframe));
|
||||
}
|
||||
|
||||
|
|
@ -1135,35 +1222,6 @@ coro_await(PyObject *coro)
|
|||
return (PyObject *)cw;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
coro_get_cr_await(PyObject *coro, void *Py_UNUSED(ignored))
|
||||
{
|
||||
PyObject *yf = _PyGen_yf((PyGenObject *) coro);
|
||||
if (yf == NULL)
|
||||
Py_RETURN_NONE;
|
||||
return yf;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
cr_getsuspended(PyObject *self, void *Py_UNUSED(ignored))
|
||||
{
|
||||
PyCoroObject *coro = _PyCoroObject_CAST(self);
|
||||
if (FRAME_STATE_SUSPENDED(coro->cr_frame_state)) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
cr_getrunning(PyObject *self, void *Py_UNUSED(ignored))
|
||||
{
|
||||
PyCoroObject *coro = _PyCoroObject_CAST(self);
|
||||
if (coro->cr_frame_state == FRAME_EXECUTING) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
cr_getframe(PyObject *coro, void *Py_UNUSED(ignored))
|
||||
{
|
||||
|
|
@ -1181,12 +1239,12 @@ static PyGetSetDef coro_getsetlist[] = {
|
|||
PyDoc_STR("name of the coroutine")},
|
||||
{"__qualname__", gen_get_qualname, gen_set_qualname,
|
||||
PyDoc_STR("qualified name of the coroutine")},
|
||||
{"cr_await", coro_get_cr_await, NULL,
|
||||
{"cr_await", gen_getyieldfrom, NULL,
|
||||
PyDoc_STR("object being awaited on, or None")},
|
||||
{"cr_running", cr_getrunning, NULL, NULL},
|
||||
{"cr_running", gen_getrunning, NULL, NULL},
|
||||
{"cr_frame", cr_getframe, NULL, NULL},
|
||||
{"cr_code", cr_getcode, NULL, NULL},
|
||||
{"cr_suspended", cr_getsuspended, NULL, NULL},
|
||||
{"cr_suspended", gen_getsuspended, NULL, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
|
@ -1602,26 +1660,16 @@ ag_getcode(PyObject *gen, void *Py_UNUSED(ignored))
|
|||
return _gen_getcode((PyGenObject*)gen, "ag_code");
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
ag_getsuspended(PyObject *self, void *Py_UNUSED(ignored))
|
||||
{
|
||||
PyAsyncGenObject *ag = _PyAsyncGenObject_CAST(self);
|
||||
if (FRAME_STATE_SUSPENDED(ag->ag_frame_state)) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
static PyGetSetDef async_gen_getsetlist[] = {
|
||||
{"__name__", gen_get_name, gen_set_name,
|
||||
PyDoc_STR("name of the async generator")},
|
||||
{"__qualname__", gen_get_qualname, gen_set_qualname,
|
||||
PyDoc_STR("qualified name of the async generator")},
|
||||
{"ag_await", coro_get_cr_await, NULL,
|
||||
{"ag_await", gen_getyieldfrom, NULL,
|
||||
PyDoc_STR("object being awaited on, or None")},
|
||||
{"ag_frame", ag_getframe, NULL, NULL},
|
||||
{"ag_code", ag_getcode, NULL, NULL},
|
||||
{"ag_suspended", ag_getsuspended, NULL, NULL},
|
||||
{"ag_suspended", gen_getsuspended, NULL, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1312,14 +1312,13 @@ dummy_func(
|
|||
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
|
||||
if ((tstate->interp->eval_frame == NULL) &&
|
||||
(Py_TYPE(receiver_o) == &PyGen_Type || Py_TYPE(receiver_o) == &PyCoro_Type) &&
|
||||
((PyGenObject *)receiver_o)->gi_frame_state < FRAME_EXECUTING)
|
||||
gen_try_set_executing((PyGenObject *)receiver_o))
|
||||
{
|
||||
PyGenObject *gen = (PyGenObject *)receiver_o;
|
||||
_PyInterpreterFrame *gen_frame = &gen->gi_iframe;
|
||||
_PyFrame_StackPush(gen_frame, PyStackRef_MakeHeapSafe(v));
|
||||
DEAD(v);
|
||||
SYNC_SP();
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
gen->gi_exc_state.previous_item = tstate->exc_info;
|
||||
tstate->exc_info = &gen->gi_exc_state;
|
||||
assert(INSTRUCTION_SIZE + oparg <= UINT16_MAX);
|
||||
|
|
@ -1360,12 +1359,11 @@ dummy_func(
|
|||
op(_SEND_GEN_FRAME, (receiver, v -- receiver, gen_frame)) {
|
||||
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(receiver);
|
||||
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type && Py_TYPE(gen) != &PyCoro_Type);
|
||||
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING);
|
||||
DEOPT_IF(!gen_try_set_executing((PyGenObject *)gen));
|
||||
STAT_INC(SEND, hit);
|
||||
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
|
||||
_PyFrame_StackPush(pushed_frame, PyStackRef_MakeHeapSafe(v));
|
||||
DEAD(v);
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
gen->gi_exc_state.previous_item = tstate->exc_info;
|
||||
tstate->exc_info = &gen->gi_exc_state;
|
||||
assert(INSTRUCTION_SIZE + oparg <= UINT16_MAX);
|
||||
|
|
@ -1389,7 +1387,6 @@ dummy_func(
|
|||
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
|
||||
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
|
||||
assert(oparg == 0 || oparg == 1);
|
||||
gen->gi_frame_state = FRAME_SUSPENDED + oparg;
|
||||
_PyStackRef temp = retval;
|
||||
DEAD(retval);
|
||||
SAVE_STACK();
|
||||
|
|
@ -1399,6 +1396,8 @@ dummy_func(
|
|||
_PyInterpreterFrame *gen_frame = frame;
|
||||
frame = tstate->current_frame = frame->previous;
|
||||
gen_frame->previous = NULL;
|
||||
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
|
||||
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_SUSPENDED + oparg);
|
||||
/* We don't know which of these is relevant here, so keep them equal */
|
||||
assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER);
|
||||
#if TIER_ONE
|
||||
|
|
@ -3405,18 +3404,10 @@ dummy_func(
|
|||
op(_FOR_ITER_GEN_FRAME, (iter, null -- iter, null, gen_frame)) {
|
||||
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(iter);
|
||||
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type);
|
||||
#ifdef Py_GIL_DISABLED
|
||||
// Since generators can't be used by multiple threads anyway we
|
||||
// don't need to deopt here, but this lets us work on making
|
||||
// generators thread-safe without necessarily having to
|
||||
// specialize them thread-safely as well.
|
||||
DEOPT_IF(!_PyObject_IsUniquelyReferenced((PyObject *)gen));
|
||||
#endif
|
||||
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING);
|
||||
DEOPT_IF(!gen_try_set_executing((PyGenObject *)gen));
|
||||
STAT_INC(FOR_ITER, hit);
|
||||
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
|
||||
_PyFrame_StackPush(pushed_frame, PyStackRef_None);
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
gen->gi_exc_state.previous_item = tstate->exc_info;
|
||||
tstate->exc_info = &gen->gi_exc_state;
|
||||
pushed_frame->previous = frame;
|
||||
|
|
|
|||
|
|
@ -2304,7 +2304,8 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
|
|||
{
|
||||
assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
|
||||
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
|
||||
gen->gi_frame_state = FRAME_CLEARED;
|
||||
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_CLEARED);
|
||||
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_RETURN;
|
||||
assert(tstate->exc_info == &gen->gi_exc_state);
|
||||
tstate->exc_info = gen->gi_exc_state.previous_item;
|
||||
gen->gi_exc_state.previous_item = NULL;
|
||||
|
|
@ -3979,15 +3980,13 @@ _PyEval_GetAwaitable(PyObject *iterable, int oparg)
|
|||
Py_TYPE(iterable), oparg);
|
||||
}
|
||||
else if (PyCoro_CheckExact(iter)) {
|
||||
PyObject *yf = _PyGen_yf((PyGenObject*)iter);
|
||||
if (yf != NULL) {
|
||||
/* `iter` is a coroutine object that is being
|
||||
awaited, `yf` is a pointer to the current awaitable
|
||||
being awaited on. */
|
||||
Py_DECREF(yf);
|
||||
PyCoroObject *coro = (PyCoroObject *)iter;
|
||||
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(coro->cr_frame_state);
|
||||
if (frame_state == FRAME_SUSPENDED_YIELD_FROM) {
|
||||
/* `iter` is a coroutine object that is being awaited. */
|
||||
Py_CLEAR(iter);
|
||||
_PyErr_SetString(PyThreadState_GET(), PyExc_RuntimeError,
|
||||
"coroutine is being awaited already");
|
||||
"coroutine is being awaited already");
|
||||
}
|
||||
}
|
||||
return iter;
|
||||
|
|
|
|||
|
|
@ -496,3 +496,28 @@ check_periodics(PyThreadState *tstate) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Mark the generator as executing. Returns true if the state was changed,
|
||||
// false if it was already executing or finished.
|
||||
static inline bool
|
||||
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) {
|
||||
if (_Py_atomic_compare_exchange_int8(&gen->gi_frame_state,
|
||||
&frame_state,
|
||||
FRAME_EXECUTING)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#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) {
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
94
Python/executor_cases.c.h
generated
94
Python/executor_cases.c.h
generated
|
|
@ -5823,7 +5823,7 @@
|
|||
SET_CURRENT_CACHED_VALUES(2);
|
||||
JUMP_TO_JUMP_TARGET();
|
||||
}
|
||||
if (gen->gi_frame_state >= FRAME_EXECUTING) {
|
||||
if (!gen_try_set_executing((PyGenObject *)gen)) {
|
||||
UOP_STAT_INC(uopcode, miss);
|
||||
_tos_cache1 = v;
|
||||
_tos_cache0 = receiver;
|
||||
|
|
@ -5833,7 +5833,6 @@
|
|||
STAT_INC(SEND, hit);
|
||||
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
|
||||
_PyFrame_StackPush(pushed_frame, PyStackRef_MakeHeapSafe(v));
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
gen->gi_exc_state.previous_item = tstate->exc_info;
|
||||
tstate->exc_info = &gen->gi_exc_state;
|
||||
assert( 2u + oparg <= UINT16_MAX);
|
||||
|
|
@ -5861,7 +5860,6 @@
|
|||
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
|
||||
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
|
||||
assert(oparg == 0 || oparg == 1);
|
||||
gen->gi_frame_state = FRAME_SUSPENDED + oparg;
|
||||
_PyStackRef temp = retval;
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
tstate->exc_info = gen->gi_exc_state.previous_item;
|
||||
|
|
@ -5870,6 +5868,8 @@
|
|||
_PyInterpreterFrame *gen_frame = frame;
|
||||
frame = tstate->current_frame = frame->previous;
|
||||
gen_frame->previous = NULL;
|
||||
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
|
||||
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_SUSPENDED + oparg);
|
||||
assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER);
|
||||
#if TIER_ONE
|
||||
assert(frame->instr_ptr->op.code == INSTRUMENTED_LINE ||
|
||||
|
|
@ -10859,6 +10859,81 @@
|
|||
break;
|
||||
}
|
||||
|
||||
case _FOR_ITER_GEN_FRAME_r03: {
|
||||
CHECK_CURRENT_CACHED_VALUES(0);
|
||||
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
|
||||
_PyStackRef iter;
|
||||
_PyStackRef gen_frame;
|
||||
oparg = CURRENT_OPARG();
|
||||
iter = stack_pointer[-2];
|
||||
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(iter);
|
||||
if (Py_TYPE(gen) != &PyGen_Type) {
|
||||
UOP_STAT_INC(uopcode, miss);
|
||||
SET_CURRENT_CACHED_VALUES(0);
|
||||
JUMP_TO_JUMP_TARGET();
|
||||
}
|
||||
if (!gen_try_set_executing((PyGenObject *)gen)) {
|
||||
UOP_STAT_INC(uopcode, miss);
|
||||
SET_CURRENT_CACHED_VALUES(0);
|
||||
JUMP_TO_JUMP_TARGET();
|
||||
}
|
||||
STAT_INC(FOR_ITER, hit);
|
||||
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
|
||||
_PyFrame_StackPush(pushed_frame, PyStackRef_None);
|
||||
gen->gi_exc_state.previous_item = tstate->exc_info;
|
||||
tstate->exc_info = &gen->gi_exc_state;
|
||||
pushed_frame->previous = frame;
|
||||
frame->return_offset = (uint16_t)( 2u + oparg);
|
||||
gen_frame = PyStackRef_Wrap(pushed_frame);
|
||||
_tos_cache2 = gen_frame;
|
||||
_tos_cache1 = stack_pointer[-1];
|
||||
_tos_cache0 = iter;
|
||||
SET_CURRENT_CACHED_VALUES(3);
|
||||
stack_pointer += -2;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
|
||||
break;
|
||||
}
|
||||
|
||||
case _FOR_ITER_GEN_FRAME_r13: {
|
||||
CHECK_CURRENT_CACHED_VALUES(1);
|
||||
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
|
||||
_PyStackRef iter;
|
||||
_PyStackRef gen_frame;
|
||||
_PyStackRef _stack_item_0 = _tos_cache0;
|
||||
oparg = CURRENT_OPARG();
|
||||
iter = stack_pointer[-1];
|
||||
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(iter);
|
||||
if (Py_TYPE(gen) != &PyGen_Type) {
|
||||
UOP_STAT_INC(uopcode, miss);
|
||||
_tos_cache0 = _stack_item_0;
|
||||
SET_CURRENT_CACHED_VALUES(1);
|
||||
JUMP_TO_JUMP_TARGET();
|
||||
}
|
||||
if (!gen_try_set_executing((PyGenObject *)gen)) {
|
||||
UOP_STAT_INC(uopcode, miss);
|
||||
_tos_cache0 = _stack_item_0;
|
||||
SET_CURRENT_CACHED_VALUES(1);
|
||||
JUMP_TO_JUMP_TARGET();
|
||||
}
|
||||
STAT_INC(FOR_ITER, hit);
|
||||
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
|
||||
_PyFrame_StackPush(pushed_frame, PyStackRef_None);
|
||||
gen->gi_exc_state.previous_item = tstate->exc_info;
|
||||
tstate->exc_info = &gen->gi_exc_state;
|
||||
pushed_frame->previous = frame;
|
||||
frame->return_offset = (uint16_t)( 2u + oparg);
|
||||
gen_frame = PyStackRef_Wrap(pushed_frame);
|
||||
_tos_cache2 = gen_frame;
|
||||
_tos_cache1 = _stack_item_0;
|
||||
_tos_cache0 = iter;
|
||||
SET_CURRENT_CACHED_VALUES(3);
|
||||
stack_pointer += -1;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
|
||||
break;
|
||||
}
|
||||
|
||||
case _FOR_ITER_GEN_FRAME_r23: {
|
||||
CHECK_CURRENT_CACHED_VALUES(2);
|
||||
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
|
||||
|
|
@ -10876,17 +10951,7 @@
|
|||
SET_CURRENT_CACHED_VALUES(2);
|
||||
JUMP_TO_JUMP_TARGET();
|
||||
}
|
||||
#ifdef Py_GIL_DISABLED
|
||||
|
||||
if (!_PyObject_IsUniquelyReferenced((PyObject *)gen)) {
|
||||
UOP_STAT_INC(uopcode, miss);
|
||||
_tos_cache1 = _stack_item_1;
|
||||
_tos_cache0 = iter;
|
||||
SET_CURRENT_CACHED_VALUES(2);
|
||||
JUMP_TO_JUMP_TARGET();
|
||||
}
|
||||
#endif
|
||||
if (gen->gi_frame_state >= FRAME_EXECUTING) {
|
||||
if (!gen_try_set_executing((PyGenObject *)gen)) {
|
||||
UOP_STAT_INC(uopcode, miss);
|
||||
_tos_cache1 = _stack_item_1;
|
||||
_tos_cache0 = iter;
|
||||
|
|
@ -10896,7 +10961,6 @@
|
|||
STAT_INC(FOR_ITER, hit);
|
||||
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
|
||||
_PyFrame_StackPush(pushed_frame, PyStackRef_None);
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
gen->gi_exc_state.previous_item = tstate->exc_info;
|
||||
tstate->exc_info = &gen->gi_exc_state;
|
||||
pushed_frame->previous = frame;
|
||||
|
|
|
|||
22
Python/generated_cases.c.h
generated
22
Python/generated_cases.c.h
generated
|
|
@ -5548,14 +5548,7 @@
|
|||
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
|
||||
JUMP_TO_PREDICTED(FOR_ITER);
|
||||
}
|
||||
#ifdef Py_GIL_DISABLED
|
||||
if (!_PyObject_IsUniquelyReferenced((PyObject *)gen)) {
|
||||
UPDATE_MISS_STATS(FOR_ITER);
|
||||
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
|
||||
JUMP_TO_PREDICTED(FOR_ITER);
|
||||
}
|
||||
#endif
|
||||
if (gen->gi_frame_state >= FRAME_EXECUTING) {
|
||||
if (!gen_try_set_executing((PyGenObject *)gen)) {
|
||||
UPDATE_MISS_STATS(FOR_ITER);
|
||||
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
|
||||
JUMP_TO_PREDICTED(FOR_ITER);
|
||||
|
|
@ -5563,7 +5556,6 @@
|
|||
STAT_INC(FOR_ITER, hit);
|
||||
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
|
||||
_PyFrame_StackPush(pushed_frame, PyStackRef_None);
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
gen->gi_exc_state.previous_item = tstate->exc_info;
|
||||
tstate->exc_info = &gen->gi_exc_state;
|
||||
pushed_frame->previous = frame;
|
||||
|
|
@ -7327,7 +7319,6 @@
|
|||
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
|
||||
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
|
||||
assert(oparg == 0 || oparg == 1);
|
||||
gen->gi_frame_state = FRAME_SUSPENDED + oparg;
|
||||
_PyStackRef temp = retval;
|
||||
stack_pointer += -1;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
|
|
@ -7338,6 +7329,8 @@
|
|||
_PyInterpreterFrame *gen_frame = frame;
|
||||
frame = tstate->current_frame = frame->previous;
|
||||
gen_frame->previous = NULL;
|
||||
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
|
||||
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_SUSPENDED + oparg);
|
||||
assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER);
|
||||
#if TIER_ONE
|
||||
assert(frame->instr_ptr->op.code == INSTRUMENTED_LINE ||
|
||||
|
|
@ -10301,14 +10294,13 @@
|
|||
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
|
||||
if ((tstate->interp->eval_frame == NULL) &&
|
||||
(Py_TYPE(receiver_o) == &PyGen_Type || Py_TYPE(receiver_o) == &PyCoro_Type) &&
|
||||
((PyGenObject *)receiver_o)->gi_frame_state < FRAME_EXECUTING)
|
||||
gen_try_set_executing((PyGenObject *)receiver_o))
|
||||
{
|
||||
PyGenObject *gen = (PyGenObject *)receiver_o;
|
||||
_PyInterpreterFrame *gen_frame = &gen->gi_iframe;
|
||||
_PyFrame_StackPush(gen_frame, PyStackRef_MakeHeapSafe(v));
|
||||
stack_pointer += -1;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
gen->gi_exc_state.previous_item = tstate->exc_info;
|
||||
tstate->exc_info = &gen->gi_exc_state;
|
||||
assert( 2u + oparg <= UINT16_MAX);
|
||||
|
|
@ -10401,7 +10393,7 @@
|
|||
assert(_PyOpcode_Deopt[opcode] == (SEND));
|
||||
JUMP_TO_PREDICTED(SEND);
|
||||
}
|
||||
if (gen->gi_frame_state >= FRAME_EXECUTING) {
|
||||
if (!gen_try_set_executing((PyGenObject *)gen)) {
|
||||
UPDATE_MISS_STATS(SEND);
|
||||
assert(_PyOpcode_Deopt[opcode] == (SEND));
|
||||
JUMP_TO_PREDICTED(SEND);
|
||||
|
|
@ -10409,7 +10401,6 @@
|
|||
STAT_INC(SEND, hit);
|
||||
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
|
||||
_PyFrame_StackPush(pushed_frame, PyStackRef_MakeHeapSafe(v));
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
gen->gi_exc_state.previous_item = tstate->exc_info;
|
||||
tstate->exc_info = &gen->gi_exc_state;
|
||||
assert( 2u + oparg <= UINT16_MAX);
|
||||
|
|
@ -12045,7 +12036,6 @@
|
|||
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
|
||||
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
|
||||
assert(oparg == 0 || oparg == 1);
|
||||
gen->gi_frame_state = FRAME_SUSPENDED + oparg;
|
||||
_PyStackRef temp = retval;
|
||||
stack_pointer += -1;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
|
|
@ -12056,6 +12046,8 @@
|
|||
_PyInterpreterFrame *gen_frame = frame;
|
||||
frame = tstate->current_frame = frame->previous;
|
||||
gen_frame->previous = NULL;
|
||||
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
|
||||
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_SUSPENDED + oparg);
|
||||
assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER);
|
||||
#if TIER_ONE
|
||||
assert(frame->instr_ptr->op.code == INSTRUMENTED_LINE ||
|
||||
|
|
|
|||
|
|
@ -642,6 +642,7 @@ def has_error_without_pop(op: parser.CodeDef) -> bool:
|
|||
"_PyFrame_StackPush",
|
||||
"_PyFunction_SetVersion",
|
||||
"_PyGen_GetGeneratorFromFrame",
|
||||
"gen_try_set_executing",
|
||||
"_PyInterpreterState_GET",
|
||||
"_PyList_AppendTakeRef",
|
||||
"_PyList_ITEMS",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue