py/objgenerator: Allow to pend an exception for next execution.
This implements .pend_throw(exc) method, which sets up an exception to be triggered on the next call to generator's .__next__() or .send() method. This is unlike .throw(), which immediately starts to execute the generator to process the exception. This effectively adds Future-like capabilities to generator protocol (exception will be raised in the future). The need for such a method arised to implement uasyncio wait_for() function efficiently (its behavior is clearly "Future" like, and normally would require to introduce an expensive Future wrapper around all native couroutines, like upstream asyncio does). py/objgenerator: pend_throw: Return previous pended value. This effectively allows to store an additional value (not necessary an exception) in a coroutine while it's not being executed. uasyncio has exactly this usecase: to mark a coro waiting in I/O queue (and thus not executed in the normal scheduling queue), for the purpose of implementing wait_for() function (cancellation of such waiting coro by a timeout).
This commit is contained in:
parent
f4ed2dfa94
commit
6364401666
|
@ -706,6 +706,15 @@ typedef double mp_float_t;
|
|||
#define MICROPY_PY_ASYNC_AWAIT (1)
|
||||
#endif
|
||||
|
||||
// Non-standard .pend_throw() method for generators, allowing for
|
||||
// Future-like behavior with respect to exception handling: an
|
||||
// exception set with .pend_throw() will activate on the next call
|
||||
// to generator's .send() or .__next__(). (This is useful to implement
|
||||
// async schedulers.)
|
||||
#ifndef MICROPY_PY_GENERATOR_PEND_THROW
|
||||
#define MICROPY_PY_GENERATOR_PEND_THROW (1)
|
||||
#endif
|
||||
|
||||
// Issue a warning when comparing str and bytes objects
|
||||
#ifndef MICROPY_PY_STR_BYTES_CMP_WARN
|
||||
#define MICROPY_PY_STR_BYTES_CMP_WARN (0)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2013, 2014 Damien P. George
|
||||
* Copyright (c) 2014 Paul Sokolovsky
|
||||
* Copyright (c) 2014-2017 Paul Sokolovsky
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -104,7 +104,16 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_
|
|||
mp_raise_TypeError("can't send non-None value to a just-started generator");
|
||||
}
|
||||
} else {
|
||||
*self->code_state.sp = send_value;
|
||||
#if MICROPY_PY_GENERATOR_PEND_THROW
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
mp_obj_dict_t *old_globals = mp_globals_get();
|
||||
mp_globals_set(self->globals);
|
||||
|
@ -125,6 +134,9 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_
|
|||
|
||||
case MP_VM_RETURN_YIELD:
|
||||
*ret_val = *self->code_state.sp;
|
||||
#if MICROPY_PY_GENERATOR_PEND_THROW
|
||||
*self->code_state.sp = mp_const_none;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case MP_VM_RETURN_EXCEPTION: {
|
||||
|
@ -219,10 +231,24 @@ STATIC mp_obj_t gen_instance_close(mp_obj_t self_in) {
|
|||
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_close_obj, gen_instance_close);
|
||||
|
||||
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);
|
||||
if (self->code_state.sp == self->code_state.state - 1) {
|
||||
mp_raise_TypeError("can't pend throw to just-started generator");
|
||||
}
|
||||
mp_obj_t prev = *self->code_state.sp;
|
||||
*self->code_state.sp = exc_in;
|
||||
return prev;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_pend_throw_obj, gen_instance_pend_throw);
|
||||
|
||||
STATIC const mp_rom_map_elem_t gen_instance_locals_dict_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&gen_instance_close_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&gen_instance_send_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_throw), MP_ROM_PTR(&gen_instance_throw_obj) },
|
||||
#if MICROPY_PY_GENERATOR_PEND_THROW
|
||||
{ MP_ROM_QSTR(MP_QSTR_pend_throw), MP_ROM_PTR(&gen_instance_pend_throw_obj) },
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(gen_instance_locals_dict, gen_instance_locals_dict_table);
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
def gen():
|
||||
i = 0
|
||||
while 1:
|
||||
yield i
|
||||
i += 1
|
||||
|
||||
g = gen()
|
||||
|
||||
try:
|
||||
g.pend_throw
|
||||
except AttributeError:
|
||||
print("SKIP")
|
||||
raise SystemExit
|
||||
|
||||
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
g.pend_throw(ValueError())
|
||||
|
||||
v = None
|
||||
try:
|
||||
v = next(g)
|
||||
except Exception as e:
|
||||
print("raised", repr(e))
|
||||
|
||||
print("ret was:", v)
|
|
@ -0,0 +1,4 @@
|
|||
0
|
||||
1
|
||||
raised ValueError()
|
||||
ret was: None
|
|
@ -337,7 +337,7 @@ def run_tests(pyb, tests, args, base_path="."):
|
|||
# Some tests are known to fail with native emitter
|
||||
# Remove them from the below when they work
|
||||
if args.emit == 'native':
|
||||
skip_tests.update({'basics/%s.py' % t for t in 'gen_yield_from gen_yield_from_close gen_yield_from_ducktype gen_yield_from_exc gen_yield_from_iter gen_yield_from_send gen_yield_from_stopped gen_yield_from_throw gen_yield_from_throw2 gen_yield_from_throw3 generator1 generator2 generator_args generator_close generator_closure generator_exc generator_return generator_send'.split()}) # require yield
|
||||
skip_tests.update({'basics/%s.py' % t for t in 'gen_yield_from gen_yield_from_close gen_yield_from_ducktype gen_yield_from_exc gen_yield_from_iter gen_yield_from_send gen_yield_from_stopped gen_yield_from_throw gen_yield_from_throw2 gen_yield_from_throw3 generator1 generator2 generator_args generator_close generator_closure generator_exc generator_pend_throw generator_return generator_send'.split()}) # require yield
|
||||
skip_tests.update({'basics/%s.py' % t for t in 'bytes_gen class_store_class globals_del string_join'.split()}) # require yield
|
||||
skip_tests.update({'basics/async_%s.py' % t for t in 'def await await2 for for2 with with2'.split()}) # require yield
|
||||
skip_tests.update({'basics/%s.py' % t for t in 'try_reraise try_reraise2'.split()}) # require raise_varargs
|
||||
|
|
Loading…
Reference in New Issue