py/objgenerator: Allow pend_throw to an unstarted generator.
Replace the is_running field with a tri-state variable to indicate running/not-running/pending-exception. Update tests to cover the various cases. This allows cancellation in uasyncio even if the coroutine hasn't been executed yet. Fixes #5242
This commit is contained in:
parent
576ed89224
commit
5578182ec9
@ -43,7 +43,10 @@ const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit},
|
|||||||
|
|
||||||
typedef struct _mp_obj_gen_instance_t {
|
typedef struct _mp_obj_gen_instance_t {
|
||||||
mp_obj_base_t base;
|
mp_obj_base_t base;
|
||||||
bool is_running;
|
// mp_const_none: Not-running, no exception.
|
||||||
|
// MP_OBJ_NULL: Running, no exception.
|
||||||
|
// other: Not running, pending exception.
|
||||||
|
mp_obj_t pend_exc;
|
||||||
mp_code_state_t code_state;
|
mp_code_state_t code_state;
|
||||||
} mp_obj_gen_instance_t;
|
} mp_obj_gen_instance_t;
|
||||||
|
|
||||||
@ -60,7 +63,7 @@ STATIC mp_obj_t gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, cons
|
|||||||
n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t));
|
n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t));
|
||||||
o->base.type = &mp_type_gen_instance;
|
o->base.type = &mp_type_gen_instance;
|
||||||
|
|
||||||
o->is_running = false;
|
o->pend_exc = mp_const_none;
|
||||||
o->code_state.fun_bc = self_fun;
|
o->code_state.fun_bc = self_fun;
|
||||||
o->code_state.ip = 0;
|
o->code_state.ip = 0;
|
||||||
o->code_state.n_state = n_state;
|
o->code_state.n_state = n_state;
|
||||||
@ -105,7 +108,7 @@ STATIC mp_obj_t native_gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_k
|
|||||||
o->base.type = &mp_type_gen_instance;
|
o->base.type = &mp_type_gen_instance;
|
||||||
|
|
||||||
// Parse the input arguments and set up the code state
|
// Parse the input arguments and set up the code state
|
||||||
o->is_running = false;
|
o->pend_exc = mp_const_none;
|
||||||
o->code_state.fun_bc = self_fun;
|
o->code_state.fun_bc = self_fun;
|
||||||
o->code_state.ip = (const byte*)prelude_offset;
|
o->code_state.ip = (const byte*)prelude_offset;
|
||||||
o->code_state.n_state = n_state;
|
o->code_state.n_state = n_state;
|
||||||
@ -151,28 +154,30 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_
|
|||||||
*ret_val = MP_OBJ_STOP_ITERATION;
|
*ret_val = MP_OBJ_STOP_ITERATION;
|
||||||
return MP_VM_RETURN_NORMAL;
|
return MP_VM_RETURN_NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure the generator cannot be reentered during execution
|
||||||
|
if (self->pend_exc == MP_OBJ_NULL) {
|
||||||
|
mp_raise_ValueError("generator already executing");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MICROPY_PY_GENERATOR_PEND_THROW
|
||||||
|
// If exception is pending (set using .pend_throw()), process it now.
|
||||||
|
if (self->pend_exc != mp_const_none) {
|
||||||
|
throw_value = self->pend_exc;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// If the generator is started, allow sending a value.
|
||||||
if (self->code_state.sp == self->code_state.state - 1) {
|
if (self->code_state.sp == self->code_state.state - 1) {
|
||||||
if (send_value != mp_const_none) {
|
if (send_value != mp_const_none) {
|
||||||
mp_raise_TypeError("can't send non-None value to a just-started generator");
|
mp_raise_TypeError("can't send non-None value to a just-started generator");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#if MICROPY_PY_GENERATOR_PEND_THROW
|
*self->code_state.sp = send_value;
|
||||||
// If exception is pending (set using .pend_throw()), process it now.
|
|
||||||
if (*self->code_state.sp != mp_const_none) {
|
|
||||||
throw_value = *self->code_state.sp;
|
|
||||||
*self->code_state.sp = MP_OBJ_NULL;
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
*self->code_state.sp = send_value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the generator cannot be reentered during execution
|
// Mark as running
|
||||||
if (self->is_running) {
|
self->pend_exc = MP_OBJ_NULL;
|
||||||
mp_raise_ValueError("generator already executing");
|
|
||||||
}
|
|
||||||
self->is_running = true;
|
|
||||||
|
|
||||||
// Set up the correct globals context for the generator and execute it
|
// Set up the correct globals context for the generator and execute it
|
||||||
self->code_state.old_globals = mp_globals_get();
|
self->code_state.old_globals = mp_globals_get();
|
||||||
@ -195,7 +200,8 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_
|
|||||||
|
|
||||||
mp_globals_set(self->code_state.old_globals);
|
mp_globals_set(self->code_state.old_globals);
|
||||||
|
|
||||||
self->is_running = false;
|
// Mark as not running
|
||||||
|
self->pend_exc = mp_const_none;
|
||||||
|
|
||||||
switch (ret_kind) {
|
switch (ret_kind) {
|
||||||
case MP_VM_RETURN_NORMAL:
|
case MP_VM_RETURN_NORMAL:
|
||||||
@ -313,11 +319,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_close_obj, gen_instance_close);
|
|||||||
#if MICROPY_PY_GENERATOR_PEND_THROW
|
#if MICROPY_PY_GENERATOR_PEND_THROW
|
||||||
STATIC mp_obj_t gen_instance_pend_throw(mp_obj_t self_in, mp_obj_t exc_in) {
|
STATIC mp_obj_t gen_instance_pend_throw(mp_obj_t self_in, mp_obj_t exc_in) {
|
||||||
mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in);
|
mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in);
|
||||||
if (self->code_state.sp == self->code_state.state - 1) {
|
if (self->pend_exc == MP_OBJ_NULL) {
|
||||||
mp_raise_TypeError("can't pend throw to just-started generator");
|
mp_raise_ValueError("generator already executing");
|
||||||
}
|
}
|
||||||
mp_obj_t prev = *self->code_state.sp;
|
mp_obj_t prev = self->pend_exc;
|
||||||
*self->code_state.sp = exc_in;
|
self->pend_exc = exc_in;
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_pend_throw_obj, gen_instance_pend_throw);
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_pend_throw_obj, gen_instance_pend_throw);
|
||||||
|
@ -13,6 +13,7 @@ except AttributeError:
|
|||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
|
|
||||||
|
# Verify that an injected exception will be raised from next().
|
||||||
print(next(g))
|
print(next(g))
|
||||||
print(next(g))
|
print(next(g))
|
||||||
g.pend_throw(ValueError())
|
g.pend_throw(ValueError())
|
||||||
@ -25,7 +26,76 @@ except Exception as e:
|
|||||||
|
|
||||||
print("ret was:", v)
|
print("ret was:", v)
|
||||||
|
|
||||||
|
|
||||||
|
# Verify that pend_throw works on an unstarted coroutine.
|
||||||
|
g = gen()
|
||||||
|
g.pend_throw(OSError())
|
||||||
try:
|
try:
|
||||||
gen().pend_throw(ValueError())
|
next(g)
|
||||||
except TypeError:
|
except Exception as e:
|
||||||
print("TypeError")
|
print("raised", repr(e))
|
||||||
|
|
||||||
|
|
||||||
|
# Verify that you can't resume the coroutine from within the running coroutine.
|
||||||
|
def gen_next():
|
||||||
|
next(g)
|
||||||
|
yield 1
|
||||||
|
|
||||||
|
g = gen_next()
|
||||||
|
|
||||||
|
try:
|
||||||
|
next(g)
|
||||||
|
except Exception as e:
|
||||||
|
print("raised", repr(e))
|
||||||
|
|
||||||
|
|
||||||
|
# Verify that you can't pend_throw from within the running coroutine.
|
||||||
|
def gen_pend_throw():
|
||||||
|
g.pend_throw(ValueError())
|
||||||
|
yield 1
|
||||||
|
|
||||||
|
g = gen_pend_throw()
|
||||||
|
|
||||||
|
try:
|
||||||
|
next(g)
|
||||||
|
except Exception as e:
|
||||||
|
print("raised", repr(e))
|
||||||
|
|
||||||
|
|
||||||
|
# Verify that the pend_throw exception can be ignored.
|
||||||
|
class CancelledError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def gen_cancelled():
|
||||||
|
for i in range(5):
|
||||||
|
try:
|
||||||
|
yield i
|
||||||
|
except CancelledError:
|
||||||
|
print('ignore CancelledError')
|
||||||
|
|
||||||
|
g = gen_cancelled()
|
||||||
|
print(next(g))
|
||||||
|
g.pend_throw(CancelledError())
|
||||||
|
print(next(g))
|
||||||
|
# ...but not if the generator hasn't started.
|
||||||
|
g = gen_cancelled()
|
||||||
|
g.pend_throw(CancelledError())
|
||||||
|
try:
|
||||||
|
next(g)
|
||||||
|
except Exception as e:
|
||||||
|
print("raised", repr(e))
|
||||||
|
|
||||||
|
|
||||||
|
# Verify that calling pend_throw returns the previous exception.
|
||||||
|
g = gen()
|
||||||
|
next(g)
|
||||||
|
print(repr(g.pend_throw(CancelledError())))
|
||||||
|
print(repr(g.pend_throw(OSError)))
|
||||||
|
|
||||||
|
|
||||||
|
# Verify that you can pend_throw(None) to cancel a previous pend_throw.
|
||||||
|
g = gen()
|
||||||
|
next(g)
|
||||||
|
g.pend_throw(CancelledError())
|
||||||
|
print(repr(g.pend_throw(None)))
|
||||||
|
print(next(g))
|
||||||
|
@ -2,4 +2,14 @@
|
|||||||
1
|
1
|
||||||
raised ValueError()
|
raised ValueError()
|
||||||
ret was: None
|
ret was: None
|
||||||
TypeError
|
raised OSError()
|
||||||
|
raised ValueError('generator already executing',)
|
||||||
|
raised ValueError('generator already executing',)
|
||||||
|
0
|
||||||
|
ignore CancelledError
|
||||||
|
1
|
||||||
|
raised CancelledError()
|
||||||
|
None
|
||||||
|
CancelledError()
|
||||||
|
CancelledError()
|
||||||
|
1
|
||||||
|
Loading…
Reference in New Issue
Block a user