Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Make raising SystemExit do a soft reset on bare-metal targets #15486

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

Conversation

dpgeorge
Copy link
Member

Summary

The current situation with SystemExit and soft reset is the following:

  • sys.exit() follows CPython and just raises SystemExit
  • on the unix port, raising SystemExit quits the application / MicroPython, whether at the REPL or in code (this follows CPython behaviour)
  • on bare-metal ports, raising SystemExit at the REPL does nothing, raising it in code will stop the code and drop into the REPL
  • machine.soft_reset() raises SystemExit but with a special flag set, and bare-metal targets check this flag when it propagates to the top-level and do a soft reset when they receive it

The original idea here was that a bare-metal target can't "quit" like the unix port can, and so dropping to the REPL was considered the same as "quit". But this bare-metal behaviour is arguably inconsistent with unix.

This PR proposes to change the behaviour to the following, which is more consistent:

  • raising SystemExit on a bare-metal port will do a soft reset (unless the exception is caught by the application)
  • machine.soft_reset() is now equivalent to sys.exit()
  • unix port behaviour remains unchanged

Testing

Tested running the test suite on PYBD-SF6 and everything still passes (in particular tests that skip by raising SystemExit still correctly skip).

Trade-offs and Alternatives

This decreases code size, simplifies behaviour and makes unix and bare-metal behaviour match (and match CPython).

It is a breaking change though, if users are expecting SystemExit to just drop to the REPL. The alternative would be to not make this change.

@dpgeorge
Copy link
Member Author

This is related to #12802 which makes a similar change, but I think it's worth putting this in a dedicated PR for visibility.

Copy link

github-actions bot commented Jul 18, 2024

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:   -16 -0.009% 
   unix x64:  -120 -0.014% standard
      stm32:   -48 -0.012% PYBV10[incl -8(bss)]
     mimxrt:   -40 -0.011% TEENSY40
        rp2:   -44 -0.005% RPI_PICO_W[incl -4(bss)]
       samd:   -52 -0.020% ADAFRUIT_ITSYBITSY_M4_EXPRESS[incl -4(bss)]

@dpgeorge dpgeorge force-pushed the shared-runtime-system-exit-always-forced-exit branch from fe997bd to 1d95f35 Compare July 18, 2024 04:01
@dpgeorge dpgeorge added extmod Relates to extmod/ directory in source shared Relates to shared/ directory in source labels Jul 18, 2024
@dpgeorge dpgeorge added this to the release-1.24.0 milestone Jul 18, 2024
Copy link

codecov bot commented Jul 18, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.42%. Comparing base (1548132) to head (1d95f35).

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #15486   +/-   ##
=======================================
  Coverage   98.42%   98.42%           
=======================================
  Files         161      161           
  Lines       21252    21252           
=======================================
  Hits        20918    20918           
  Misses        334      334           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@andrewleech
Copy link
Contributor

Ah, for some reason I had in my head that pyexec_system_exit was only being used on unix port, not for the bare metal ones.
I see now in this isolated PR how it's used in the embedded ports to control the soft reset.

Regardless, this looks good to me and I think it makes sense for SystemExit to soft-reboot by default.

It's certainly true that there could be other users out there expecting to use it as a silent drop to repl, but that seems fairly unlikley to me?
Either way, said users could still catch it and drop to repl themselves in their main.py as a fairly simple workaround.

@dpgeorge
Copy link
Member Author

It's certainly true that there could be other users out there expecting to use it as a silent drop to repl, but that seems fairly unlikley to me?

Yes, it's possible some code is relying on this behaviour. But it would be an uncommon use, I'm guessing.

Either way, said users could still catch it and drop to repl themselves in their main.py as a fairly simple workaround.

Exactly. And doing it that way (with an except SystemExit) would be more explicit in the code, what the intended behaviour is.

@danicampora
Copy link
Member

I like this change! since an embedded system is supposed to run indefinitely after being deployed, so doing a soft reset after a SystemExit seems more consistent and logical.

@Gadgetoid
Copy link
Contributor

+1 to this. It makes sense and prevents surprises related to DMA, PIO, etc allocation on rp2 at least. I don't believe we have any deployments or devices using SystemExit to mean "exit to repl" at the moment, but if we do it's just another thing for us to fix.

dpgeorge added 2 commits July 20, 2024 12:13
The current situation with SystemExit and soft reset is the following:
- `sys.exit()` follows CPython and just raises `SystemExit`.
- On the unix port, raising `SystemExit` quits the application/MicroPython,
  whether at the REPL or in code (this follows CPython behaviour).
- On bare-metal ports, raising `SystemExit` at the REPL does nothing,
  raising it in code will stop the code and drop into the REPL.
- `machine.soft_reset()` raises `SystemExit` but with a special flag set,
  and bare-metal targets check this flag when it propagates to the
  top-level and do a soft reset when they receive it.

The original idea here was that a bare-metal target can't "quit" like the
unix port can, and so dropping to the REPL was considered the same as
"quit".  But this bare-metal behaviour is arguably inconsistent with unix,
and "quit" should mean terminate everything, including REPL access.

This commit changes the behaviour to the following, which is more
consistent:
- Raising `SystemExit` on a bare-metal port will do a soft reset (unless
  the exception is caught by the application).
- `machine.soft_reset()` is now equivalent to `sys.exit()`.
- unix port behaviour remains unchanged.

Tested running the test suite on an stm32 board and everything still
passes, in particular tests that skip by raising `SystemExit` still
correctly skip.

Signed-off-by: Damien George <[email protected]>
It does the same thing, raising `SystemExit`.

Signed-off-by: Damien George <[email protected]>
@dpgeorge dpgeorge force-pushed the shared-runtime-system-exit-always-forced-exit branch from 1d95f35 to 5f3ecc2 Compare July 20, 2024 02:18
@dpgeorge
Copy link
Member Author

Thanks for the reviews!

@dpgeorge dpgeorge merged commit 5f3ecc2 into micropython:master Jul 20, 2024
60 of 61 checks passed
@dpgeorge dpgeorge deleted the shared-runtime-system-exit-always-forced-exit branch July 20, 2024 02:32
@andrewleech
Copy link
Contributor

Nice one, I'll rebase my PR onto master

@dpgeorge
Copy link
Member Author

Unfortunately this change is not fully compatible with CPython behaviour.

Running CPython with -i for inspect mode, and running a script that doe raise SystemExit (or sys.exit()), the script finishes and then it drops to the REPL. You need to issue a second raise SystemExit to actually exit from the REPL back to the shell:

$ cat test.py
print(123)
raise SystemExit
$ python -i test.py
123
Traceback (most recent call last):
  File "test.py", line 2, in <module>
    raise SystemExit
SystemExit
>>>
>>> raise SystemExit
$

But with this PR (which is merged), on bare-metal a raise SystemExit in a running program, eg main.py, will exit the program and skip the REPL and go straight to a soft reset (similar CPython exiting).

If we consider the REPL on bare-metal to be the equivalent of -i inspect mode in CPython, then raise SystemExit should drop to the REPL, which is what it used to do, but now it doesn't 😞


I see two options:

  1. Leave things as they currently are, raise SystemExit does a soft reset (ie doesn't drop to the REPL). The justification is that bare-metal ports are already different enough to CPython on a desktop (eg no sys.args, raw vs friendly REPL, etc) that this additional difference doesn't matter.
  2. Revert the change here so raise SystemExit drops to the REPL. And then machine.soft_reset() is used to skip the REPL and go straight to a soft reset.

Note that option (1) means there's no clean way for an application to abort and drop to the REPL. You would need to create a custom exception derived from BaseException and raise that (eg raise SystemAbort). I guess that's acceptable.

@projectgus
Copy link
Contributor

If we consider the REPL on bare-metal to be the equivalent of -i inspect mode in CPython, then raise SystemExit should drop to the REPL, which is what it used to do, but now it doesn't 😞

@dpgeorge I don't quite follow: if this is optional behaviour on CPython (enabled by -i) then why would we consider MicroPython's default behaviour to be the equivalent of that rather than the equivalent of CPython's default behaviour?

@dpgeorge
Copy link
Member Author

I don't quite follow: if this is optional behaviour on CPython (enabled by -i) then why would we consider MicroPython's default behaviour to be the equivalent of that rather than the equivalent of CPython's default behaviour?

That's a good point, and maybe that is the right way to look at it.

My thinking was that bare-metal has inspect mode that is switched on by default. Considering you can't specify command-line options on bare-metal, this seems reasonable? Bare-metal is kind of like CPython's python -i boot.py main.py (except you can't run two scripts in succession like that in the same namespace).

@projectgus
Copy link
Contributor

Bare-metal is kind of like CPython's python -i boot.py main.py (except you can't run two scripts in succession like that in the same namespace).

Oh, I see the reasoning now. I guess, as you say, MicroPython on bare metal doesn't cleanly map to either "inspect mode" or regular CPython behaviour at the moment - but I can see the resemblance to inspect mode.

Note that option (1) means there's no clean way for an application to abort and drop to the REPL. You would need to create a custom exception derived from BaseException and raise that (eg raise SystemAbort). I guess that's acceptable.

It's hacky but another option would be to programmatically raise KeyboardInterrupt, yes?

For my 2c, having an automatically executed embedded application automatically drop to a REPL on "exit" seems more like a footgun than useful behaviour in most circumstances, but it's possible I'm not thinking through all the ways people might want to use this.

@dpgeorge
Copy link
Member Author

It's hacky but another option would be to programmatically raise KeyboardInterrupt, yes?

Yes. Or just raise BaseException (which usually nothing ever catches).

For my 2c, having an automatically executed embedded application automatically drop to a REPL on "exit" seems more like a footgun than useful behaviour in most circumstances

Yes, I tend to agree with that. You could then argue that we shouldn't ever drop to the REPL (eg on an uncaught exception), but then things like mpremote would never work. So we need to make some compromise somewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
extmod Relates to extmod/ directory in source shared Relates to shared/ directory in source
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants