summaryrefslogtreecommitdiff
path: root/py
diff options
context:
space:
mode:
authorDamien George <damien@micropython.org>2022-12-16 17:31:21 +1100
committerDamien George <damien@micropython.org>2023-03-21 18:08:57 +1100
commitd54208a2ff2ff8c2104597f715a586541ac6e663 (patch)
treec5f98cfef91106a097c9843c93aa0c4d7bb0d3cc /py
parent5d4bfce034ace816d67081d6286185d2e35b7125 (diff)
py/scheduler: Implement VM abort flag and mp_sched_vm_abort().
This is intended to be used by the very outer caller of the VM/runtime. It allows setting a top-level NLR handler that can be jumped to directly, in order to forcefully abort the VM/runtime. Enable using: #define MICROPY_ENABLE_VM_ABORT (1) Set up the handler at the top level using: nlr_buf_t nlr; nlr.ret_val = NULL; if (nlr_push(&nlr) == 0) { nlr_set_abort(&nlr); // call into the VM/runtime ... nlr_pop(); } else { if (nlr.ret_val == NULL) { // handle abort ... } else { // handle other exception that propagated to the top level ... } } nlr_set_abort(NULL); Schedule an abort, eg from an interrupt handler, using: mp_sched_vm_abort(); Signed-off-by: Damien George <damien@micropython.org>
Diffstat (limited to 'py')
-rw-r--r--py/mpconfig.h9
-rw-r--r--py/mpstate.h5
-rw-r--r--py/nlr.c7
-rw-r--r--py/nlr.h17
-rw-r--r--py/runtime.h3
-rw-r--r--py/scheduler.c19
-rw-r--r--py/vm.c4
7 files changed, 62 insertions, 2 deletions
diff --git a/py/mpconfig.h b/py/mpconfig.h
index db9c623f4..80e58d1cc 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -909,6 +909,11 @@ typedef double mp_float_t;
#define MICROPY_INTERNAL_PRINTF_PRINTER (&mp_plat_print)
#endif
+// Whether to support mp_sched_vm_abort to asynchronously abort to the top level.
+#ifndef MICROPY_ENABLE_VM_ABORT
+#define MICROPY_ENABLE_VM_ABORT (0)
+#endif
+
// Support for internal scheduler
#ifndef MICROPY_ENABLE_SCHEDULER
#define MICROPY_ENABLE_SCHEDULER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
@@ -1740,6 +1745,10 @@ typedef double mp_float_t;
#define MICROPY_WRAP_MP_SCHED_SCHEDULE(f) f
#endif
+#ifndef MICROPY_WRAP_MP_SCHED_VM_ABORT
+#define MICROPY_WRAP_MP_SCHED_VM_ABORT(f) f
+#endif
+
/*****************************************************************************/
/* Miscellaneous settings */
diff --git a/py/mpstate.h b/py/mpstate.h
index 4c9380097..f6b911af5 100644
--- a/py/mpstate.h
+++ b/py/mpstate.h
@@ -226,6 +226,11 @@ typedef struct _mp_state_vm_t {
uint8_t sched_idx;
#endif
+ #if MICROPY_ENABLE_VM_ABORT
+ bool vm_abort;
+ nlr_buf_t *nlr_abort;
+ #endif
+
#if MICROPY_PY_THREAD_GIL
// This is a global mutex used to make the VM/runtime thread-safe.
mp_thread_mutex_t gil_mutex;
diff --git a/py/nlr.c b/py/nlr.c
index a35e7d229..92db8ffb1 100644
--- a/py/nlr.c
+++ b/py/nlr.c
@@ -49,3 +49,10 @@ void nlr_pop(void) {
nlr_buf_t **top = &MP_STATE_THREAD(nlr_top);
*top = (*top)->prev;
}
+
+#if MICROPY_ENABLE_VM_ABORT
+NORETURN void nlr_jump_abort(void) {
+ MP_STATE_THREAD(nlr_top) = MP_STATE_VM(nlr_abort);
+ nlr_jump(NULL);
+}
+#endif
diff --git a/py/nlr.h b/py/nlr.h
index 338a67570..09ef66ef7 100644
--- a/py/nlr.h
+++ b/py/nlr.h
@@ -101,9 +101,16 @@
typedef struct _nlr_buf_t nlr_buf_t;
struct _nlr_buf_t {
- // the entries here must all be machine word size
+ // The entries in this struct must all be machine word size.
+
+ // Pointer to the previous nlr_buf_t in the chain.
+ // Or NULL if it's the top-level one.
nlr_buf_t *prev;
- void *ret_val; // always a concrete object (an exception instance)
+
+ // The exception that is being raised:
+ // - NULL means the jump is because of a VM abort (only if MICROPY_ENABLE_VM_ABORT enabled)
+ // - otherwise it's always a concrete object (an exception instance)
+ void *ret_val;
#if MICROPY_NLR_SETJMP
jmp_buf jmpbuf;
@@ -149,6 +156,12 @@ unsigned int nlr_push_tail(nlr_buf_t *top);
void nlr_pop(void);
NORETURN void nlr_jump(void *val);
+#if MICROPY_ENABLE_VM_ABORT
+#define nlr_set_abort(buf) MP_STATE_VM(nlr_abort) = buf
+#define nlr_get_abort() MP_STATE_VM(nlr_abort)
+NORETURN void nlr_jump_abort(void);
+#endif
+
// This must be implemented by a port. It's called by nlr_jump
// if no nlr buf has been pushed. It must not return, but rather
// should bail out with a fatal error.
diff --git a/py/runtime.h b/py/runtime.h
index 36b3caa6c..d57c25c92 100644
--- a/py/runtime.h
+++ b/py/runtime.h
@@ -75,6 +75,9 @@ void mp_deinit(void);
void mp_sched_exception(mp_obj_t exc);
void mp_sched_keyboard_interrupt(void);
+#if MICROPY_ENABLE_VM_ABORT
+void mp_sched_vm_abort(void);
+#endif
void mp_handle_pending(bool raise_exc);
#if MICROPY_ENABLE_SCHEDULER
diff --git a/py/scheduler.c b/py/scheduler.c
index db090b099..165b26dc8 100644
--- a/py/scheduler.c
+++ b/py/scheduler.c
@@ -51,6 +51,12 @@ void MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(mp_sched_keyboard_interrupt)(void)
}
#endif
+#if MICROPY_ENABLE_VM_ABORT
+void MICROPY_WRAP_MP_SCHED_VM_ABORT(mp_sched_vm_abort)(void) {
+ MP_STATE_VM(vm_abort) = true;
+}
+#endif
+
#if MICROPY_ENABLE_SCHEDULER
#define IDX_MASK(i) ((i) & (MICROPY_SCHEDULER_DEPTH - 1))
@@ -203,6 +209,17 @@ MP_REGISTER_ROOT_POINTER(mp_sched_item_t sched_queue[MICROPY_SCHEDULER_DEPTH]);
// Called periodically from the VM or from "waiting" code (e.g. sleep) to
// process background tasks and pending exceptions (e.g. KeyboardInterrupt).
void mp_handle_pending(bool raise_exc) {
+ // Handle pending VM abort.
+ #if MICROPY_ENABLE_VM_ABORT
+ if (MP_STATE_VM(vm_abort) && mp_thread_is_main_thread()) {
+ MP_STATE_VM(vm_abort) = false;
+ if (raise_exc && nlr_get_abort() != NULL) {
+ nlr_jump_abort();
+ }
+ }
+ #endif
+
+ // Handle any pending exception.
if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL) {
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
mp_obj_t obj = MP_STATE_THREAD(mp_pending_exception);
@@ -215,6 +232,8 @@ void mp_handle_pending(bool raise_exc) {
}
MICROPY_END_ATOMIC_SECTION(atomic_state);
}
+
+ // Handle any pending callbacks.
#if MICROPY_ENABLE_SCHEDULER
if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {
mp_sched_run_pending();
diff --git a/py/vm.c b/py/vm.c
index 9273dda02..385d13ee4 100644
--- a/py/vm.c
+++ b/py/vm.c
@@ -1333,6 +1333,10 @@ pending_exception_check:
// No scheduler: Just check pending exception.
MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL
#endif
+ #if MICROPY_ENABLE_VM_ABORT
+ // Check if the VM should abort execution.
+ || MP_STATE_VM(vm_abort)
+ #endif
) {
MARK_EXC_IP_SELECTIVE();
mp_handle_pending(true);