8000 py/objexcept: Add uncatchable SystemAbort exception. by laurensvalk · Pull Request #9949 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

py/objexcept: Add uncatchable SystemAbort exception. #9949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

laurensvalk
Copy link
Contributor
@laurensvalk laurensvalk commented Nov 14, 2022

This adds an exception type that forces MicroPython to exit even if the user MicroPython script catches all exceptions.

This exception can be raised by C code to force MicroPython to exit for safety reasons, such as very low battery or very high temperatures.

I did not add it to the builtins dictionary to avoid adding new non-Python features to the language so it's just for internal use. Should there be a feature guard to avoid increasing codesize?

This is my first time looking around in vm.c so I'm not quite sure this is the right way to do it. Feel free to consider this as a draft for alternative implementations.


Context

This issue was briefly discussed in the most recent MicroPython meetup. Damien's comments are at 26:00 onwards.

image

@codecov-commenter
Copy link

Codecov Report

Merging #9949 (07802c2) into master (e35bcb0) will decrease coverage by 0.00%.
The diff coverage is n/a.

@@            Coverage Diff             @@
##           master    #9949      +/-   ##
==========================================
- Coverage   98.33%   98.33%   -0.01%     
==========================================
  Files         156      156              
  Lines       20520    20519       -1     
==========================================
- Hits        20179    20178       -1     
  Misses        341      341              
Impacted Files Coverage Δ
py/obj.h 100.00% <ø> (ø)
py/objexcept.c 96.42% <ø> (ø)
py/vm.c 100.00% <ø> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@jimmo
Copy link
Member
jimmo commented Nov 14, 2022

Thanks @laurensvalk

Implementation-wise this makes sense to me to solve the scenario you described at the meetup, but I wondered whether we maybe need to consider cases where Python code is protected by a NLR scope that isn't managed by the VM.

One example would be when the scheduler invokes a function, via mp_call_function_1_protected. In this case, the VM calls mp_sched_run_pending, which ultimately calls the scheduled function via mp_call_function_1_protected, which will handle the SystemAbort. So we might want to modify mp_call_function_1_protected to re-raise the SystemAbort up to the VM NLR scope.

But, if we make mp_call_function_1_protected re-raise, then we need to ensure it's not used anywhere where it's not inside the VM's NLR scope, for example in an interrupt handler. (FWIW I did a quick search and didn't see any, and all the interrupt handlers I checked did the nlr_push explicitly). This would mean that raising SystemAbort would not be effective in an ISR, but in the slide you presented it looked like you weren't in ISR context (because you were calling mp_handle_pending).

@laurensvalk
Copy link
Contributor Author

Thanks Jim.

in the slide you presented it looked like you weren't in ISR context

Indeed, we currently only use it on the main thread. For full context, we'd use it like this:

// callback for when stop button is pressed (force_stop=false),
// or when shutdown is requested by firmware (force_stop=true)
void pbsys_main_stop_program(bool force_stop) {

    static const mp_obj_tuple_t args = {
        .base = { .type = &mp_type_tuple },
        .len = 1,
        .items = { MP_ROM_QSTR(MP_QSTR_stop_space_button_space_pressed) },
    };
    static mp_obj_exception_t system_exit;

    // Schedule SystemExit exception.
    system_exit.base.type = force_stop ? &mp_type_SystemAbort : &mp_type_SystemExit;
    system_exit.traceback_alloc = 0;
    system_exit.traceback_len = 0;
    system_exit.traceback_data = NULL;
    system_exit.args = (mp_obj_tuple_t *)&args;
    MP_STATE_MAIN_THREAD(mp_pending_exception) = MP_OBJ_FROM_PTR(&system_exit);
    #if MICROPY_ENABLE_SCHEDULER
    if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {
        MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
    }
    #endif
}

This code is mostly in use already, but in today's release we only have mp_type_SystemExit. The addition enabled by this PR is the force_stop that triggers mp_type_SystemAbort. (pybricks/pybricks-micropython#117)

@dpgeorge dpgeorge added the py-core Relates to py/ directory in source label Nov 15, 2022
@dpgeorge
Copy link
Member

This looks very clean! I thought it would be more difficult/intrusive.

If we accept this, it would need to be guarded by a config option, eg MICROPY_ENABLE_SYSTEM_ABORT.

but I wondered whether we maybe need to consider cases where Python code is protected by a NLR scope that isn't managed by the VM.

In the case of Python IRQ handlers (eg scheduled functions) I don't think there is any problem. Because already a KeyboardInterrupt is always correctly passed to the main thread, it's never handled by any scheduled handler. This is because when a scheduled function runs it locks the scheduler, which also locks processing of asynchronous exceptions (which is what KeyboardInterrupt is, and also what SystemAbort could be).

@jimmo
Copy link
Member
jimmo commented Nov 15, 2022

In the case of Python IRQ handlers (eg scheduled functions) I don't think there is any problem. Because already a KeyboardInterrupt is always correctly passed to the main thread, it's never handled by any scheduled handler. This is because when a scheduled function runs it locks the scheduler, which also locks processing of asynchronous exceptions (which is what KeyboardInterrupt is, and also what SystemAbort could be).

Yes, this makes sense under the assumption that SystemAbort works like KeyboardInterrupt in that it's injected from the outside (which I think is what @laurensvalk 's use case is?). But what if some C code, that happens to be called from a scheduled function, does mp_raise_type(&mp_type_SystemAbort).

@laurensvalk
Copy link
Contributor Author
laurensvalk commented Nov 15, 2022

Perhaps we could have

mp_sched_system_abort()

To encourage usage just like mp_sched_keyboard_interrupt instead of doing mp_raise_type(&mp_type_SystemAbort)?

E.g.

// Exception that can't be raised or caught by user code. Do not raise directly but
// use mp_sched_system_abort() to schedule it on the main thread.
MP_DEFINE_EXCEPTION(SystemAbort, BaseException)

@dpgeorge
Copy link
Member

Perhaps we could have

mp_sched_system_abort()

Yes that would be nicer. But one issue with that there can only be one pending exception, so a KeyboardInterrupt can override a SystemAbort (and vice versa).

@laurensvalk
Copy link
Contributor Author
laurensvalk commented Nov 15, 2022

Yes that would be nicer.

Thanks for looking at this. I'll update the PR.

But one issue with that there can only be one pending exception, so a KeyboardInterrupt can override a SystemAbort (and vice versa).

That is a good point, and it is something we're not currently handling above (thanks!).

But thinking about it now, it might even be beneficial for this use case. The firmware event loop could call mp_sched_system_abort multiple times without problems (while the emergency condition holds), as opposed to mp_raise_type(&mp_type_SystemAbort) where more care is needed where it is called from and how many times.

This adds an exception type that forces MicroPython to exit even if the
user MicroPython script catches all exceptions.

This exception can be raised by C code to force MicroPython to exit for
safety reasons, such as very low battery or very high temperatures.
This implements mp_sched_system_exit_or_abort(), which can be used to
schedule a SystemExit or SystemAbort on the main thread. This can be
used by async sources to exit MicroPython if requested by a firmware
process. Use abort=true if the MicroPython script must not be able to
catch it, not even with a bare except.

The implementation is similar to mp_sched_keyboard_interrupt().

Signed-off-by: Laurens Valk <laurens@pybricks.com>
@laurensvalk
Copy link
Contributor Author
  • Updated first commit to:
    • Add MICROPY_ENABLE_SYSTEM_ABORT config option as requested
    • Use mp_obj_exception_match instead of mp_obj_is_type (let me know if I should revert this)
  • Added second commit to implement mp_sched_system_exit_or_abort

I tried to follow the pattern of mp_sched_keyboard_interrupt as much as possible; let me know if something else is preferred.

@dpgeorge
Copy link
Member

See alternative in #10241.

@dpgeorge
Copy link
Member

@jimmo and I had a long discussion about this today. One thing that we think is not handled by the approach here is that it cannot abort the case where a user (accidentally) does a while 1:pass in a scheduled function. That's because (1) scheduled exceptions are not processed in scheduled functions because the scheduler is locked at that time; (2) exceptions from scheduled functions are suppressed.

Also note that mp_call_function_[12]_protected() suppress any exceptions raised within them, so would suppress a SystemAbort.

If we are going to implement a "system abort" mechanism, I think it should be as robust as possible, ie able to abort (almost) all scenarios.

@laurensvalk
Copy link
Contributor Author

If we are going to implement a "system abort" mechanism, I think it should be as robust as possible, ie able to abort (almost) all scenarios.

I completely agree. 👍

Don't worry about my PR. I suppose it's served its purpose by putting it on your radar 😄

@dpgeorge
Copy link
Member

Don't worry about my PR. I suppose it's served its purpose by putting it on your radar

Yes it lead to a good discussion. The approach here can be made to work (eg mp_call_function_[12]_protected() has to check for SystemAbort and return a value indicating it aborted). And the approach in #10241 is still not complete, in its current form it cannot abort a while 1:pass in a scheduled function (but that can be fixed).

@dpgeorge
Copy link
Member

Closing in favour of #10241.

@dpgeorge dpgeorge closed this Jan 27, 2023
tannewt pushed a commit to tannewt/circuitpython that referenced this pull request Feb 4, 2025
…-picos3

Waveshare pico S3: change the color order of the RBG LED
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
py-core Relates to py/ directory in source
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0