Skip to content

runtime/exitcodes: Set exit code according to the SystemExit exception. #15730

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

Conversation

RealJohnSmith
Copy link

Summary

Add abort setup code (nlr_set_abort) to standard runtime. This makes the standard runtime respond to abort signal without any further modifications.

  • When aborted, the program exits with 137 exit code (configurable, same as posix sig abort), to differentiate from a normal shutdown.
  • When exited by exception/crash, the program will exit with exit code 1 (configurable)
  • When exited by exception KeyboardInterrupt, the program will exit with exit code 130 (configurable, same as posix sig int)

Third time is a charm, this PR continues previous PRs, originally at #14419 and later #15392

Testing

Tested on ESP32, should work in other places. The changes are guarded by #if abort is enabled in the runtime -> only people who want the abort functionality will get this change.

Trade-offs and Alternatives

This code may return non-zero exit code in scenarios where previous version did return zero exit code. The exit codes are configurable and the defaults are zero, so not to introduce any breaking changes.

@RealJohnSmith
Copy link
Author

@dpgeorge @dlech Can you please review this code, if the changes made in the previous PR (#15392) satisfy the comments? If not, we can change it in some way, but please tell me in what way.

Copy link

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +0 +0.000% RPI_PICO_W
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS

@dpgeorge dpgeorge mentioned this pull request Aug 29, 2024
@dpgeorge dpgeorge added the py-core Relates to py/ directory in source label Aug 29, 2024
@dpgeorge
Copy link
Member

There are two independent things in this PR:

  1. Add support for VM abort to the pyexec code.
  2. Do more sophisticated handling of uncaught exceptions in pyexec.

Both of these things seem reasonable. But for (1) it would be great if this was in a separate commit, to show exactly what's changed for that. And it also needs to have a separate option to control it, eg MICROPY_PYEXEC_ENABLE_VM_ABORT. That's because some users of this function may want to enable MICROPY_ENABLE_VM_ABORT but have the abort go out to a higher level than pyexec code.

} else {
mp_obj_print(val, PRINT_STR);
mp_print_str(&mp_plat_print, "\n");
ret = PYEXEC_UNHANDLED_EXCEPTION;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the intention of printing the value here? This is quite unconventional to return a non-int value in a SystemExit, so maybe this bit can either do nothing, or call mp_obj_print_exception.

But, see also how the unix port handles this: ports/unix/main.c:handle_uncaught_exception. Maybe it's worth copying that code?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is to keep the behavior consistent with real python:

https://python.readthedocs.io/en/latest/library/exceptions.html#SystemExit

If the value is an integer, it specifies the system exit status (passed to C’s exit() function); if it is None, the exit status is zero; if it has another type (such as a string), the object’s value is printed and the exit status is one.

I know it strictly doesn't have to be and that it is something a little different, but I don't see any reason with not honoring this particular behavior.

Do you want me to change it in some way?

Copy link
Contributor

@stinos stinos Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the value is an integer,

Makes sense, but minor remark: I don't think it is 100% guaranteed (because of different object representations) that mp_obj_is_small_int(val) expresses 'is an integer value' exactly, i.e. there might be integers whos value is larger than what a small int can represent but are still a valid exit code? So perhaps this should try to extract an integer value without forcing it to be a small int?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that a valid exit code is only one byte long anyway, 0 to 255 (sometimes -1, which is equivalent to 255). So I don't think this should be an issue. Is there a case where bigger exit codes might be needed? Can you point to some documentation where they are used? What function do you suggest instead of mp_obj_is_small_int(val) ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the int is passed to the C exit function.

According to its docs, https://man7.org/linux/man-pages/man3/exit.3.html, only the 0xFF part (the least significant byte, 0-255) is masked and used.

Copy link
Contributor

@stinos stinos Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is masked and used.

Those are some version of linux-specific docs so in my understanding they only specify one particular use case 'returned to the parent when using wait'. It's easy enough to find conflicting information which shows the full 32bit can in fact be used both on unix and especially on windows where it's not uncommon; first search result: https://stackoverflow.com/questions/179565/exit-codes-bigger-than-255-possible.

What function do you suggest instead

Hmm, good question. Also depends on whether it should be an actual integer (mp_obj_get_int), or something which can be converted to an integer (mp_obj_get_int_maybe). But in any case another small issue is that those use mp_int_t not int. Probably fine to just cast the result to int though for completeness I'd document that somewhere.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mp_obj_is_small_int should be fine up to 32bits, right? I understand that this is a "pointer-size int". Is it correct?

re mp_obj_get_int_maybe - is there a method/helper, which can check whether it is castable? To run the check before the get and maybe do something else with the object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mp_obj_is_small_int should be fine up to 32bits, right? I understand that this is a "pointer-size int". Is it correct?

It's stored in a machine word, but it has 1 or 2 bits less available for the actual representation, see mpconfig.h. So not necessarily 32 bits.

re mp_obj_get_int_maybe - is there a method/helper, which can check whether it is castable? To run the check before the get and maybe do something else with the object.

On second thought mp_obj_get_int_maybe should not be used here because it can fall back to calling MP_UNARY_OP_INT_MAYBE which is probably not intended, and I doubt anyone is going to use it anyway. So probably:

if (mp_obj_is_int(val)) {
    val= (int) mp_obj_int_get_truncated(arg);
}

is ok here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I applied this and force pushed. Should be ok resolved now.

@RealJohnSmith
Copy link
Author

Thank you for your time and input. I changed it according to your comments.
Originally it used to be in two commits, but some time ago, there was a merge conflict as master changed in the meantime quite some bit, in this handling. I fixed the conflict in one go, as it would have been complicated cherry picking specific lines in which belongs where and how it resolves that conflict. Is it ok to have it as one commit after all?

@RealJohnSmith RealJohnSmith force-pushed the enable-abort-support branch 2 times, most recently from 0319862 to 1fc878b Compare August 29, 2024 08:36
Copy link

codecov bot commented Aug 29, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.43%. Comparing base (91f4a6b) to head (13cccd3).
Report is 134 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #15730      +/-   ##
==========================================
- Coverage   98.43%   98.43%   -0.01%     
==========================================
  Files         161      163       +2     
  Lines       21281    21294      +13     
==========================================
+ Hits        20948    20960      +12     
- Misses        333      334       +1     

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

@RealJohnSmith RealJohnSmith force-pushed the enable-abort-support branch 2 times, most recently from 933c6cf to 1ebff3f Compare August 29, 2024 15:13
@RealJohnSmith
Copy link
Author

Is it ok now, in its current form?

@RealJohnSmith
Copy link
Author

What blocks it from mergin?

@@ -37,6 +37,23 @@ extern pyexec_mode_kind_t pyexec_mode_kind;

#define PYEXEC_FORCED_EXIT (0x100)

#if MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING
#define PYEXEC_NORMAL_EXIT (0)
#define PYEXEC_UNHANDLED_EXCEPTION (1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does one distinguish between an unhandled exception, and a SystemExit(1)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One doesn't. That is ok. It is not distinguishable in other programs as well. Only by additional stderr output

@RealJohnSmith
Copy link
Author

Ok, I updated the code according to the comments

@RealJohnSmith
Copy link
Author

Is there anything else I should update, or is this ok to accept and merge? @dpgeorge

@RealJohnSmith
Copy link
Author

Superceded by #15863

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.

3 participants