libtock-c
libtock-c copied to clipboard
Use new exit syscall on process finish
I've been meaning to write this PR for a long time, but now that we have the exit syscall I think we should actually use it. If we had exit from the beginning I don't think we would end a process by having it sit in a yield loop, we would just call exit.
Example pconsole:
Initialization complete. Entering main loop.
Hello World!
Hi welcome to Tock. This test makes sure that a greater than 64 byte message can be printed.
And a short message.
list
PID Name Quanta Syscalls Dropped Callbacks Restarts State Grants
0 printf_long 0 17 0 0 Terminated 0/14
1 blink 0 39 0 0 Yielded 1/14
2 c_hello 0 7 0 0 Terminated 0/14
I agree with @brghena, I was counting on this behavior. I think we can use the exit if and only if we can be sure that there is no registered callback.
I think we can use the exit if and only if we can be sure that there is no registered callback.
Independent of whether this is technically feasible, I somewhat strongly feel we shouldn't introduce behavior like this. Implicitly exiting or not depending on previous application behavior would be really confusing. Imagine an application wanting to exit on process finish and relying on this behavior. Then an error anywhere in the system -- including kernel capsules -- could cause a callback to not be unsubscribed and thus the application not exiting.
We should either introduce this now, or in a subsequent breaking release of libtock-c. Applications relying on the previous behavior can presumably always include a simple while (1) { yield(); } at the end of their main, at least as a quick compatibility fix.
I think we can use the exit if and only if we can be sure that there is no registered callback.
Independent of whether this is technically feasible, I somewhat strongly feel we shouldn't introduce behavior like this. Implicitly
exiting or not depending on previous application behavior would be really confusing. Imagine an application wanting to exit on process finish and relying on this behavior. Then an error anywhere in the system -- including kernel capsules -- could cause a callback to not be unsubscribed and thus the application not exiting.
This is how nodejs works, it only exists if there is no other possible task that can be scheduled. In think this was a nice feature of Tock. In most of the cases applications will not want to exit, but schedule callbacks and wait for them. Having this done automatically when main finishes I think is a plus. Another example is services using the ipc driver.
On the other hand, applications that want to exit can use the exit system call.
We should either introduce this now, or in a subsequent breaking release of
libtock-c. Applications relying on the previous behavior can presumably always include a simplewhile (1) { yield(); }at the end of their main, at least as a quick compatibility fix.
Wouldn't this result in most of the applications ending like this?
I think one very important distinction in the way the NodeJS event loop works compared to a Tock process is that NodeJS seems to have rather sophisticated mechanisms to check whether an event is pending or can possibly arrive (as outlined here). In contrast to that, a subscription in Tock only allows to make conclusions about whether events could arrive at all, not if, or when, or how many.
Thus imagine the following case: an application subscribes a callback, errors somewhere, then does not unsubscribe -- even though there is no event scheduled which could possibly result in the callback scheduled. Now, as a result of runtime behaviour, the application would never exit, thus never possibly be scheduled to restart or free up resources for other processes.
I'm not saying that this programming paradigm is not valid, just that it should be an explicit decision. An app exiting once it reaches the end of its main is very explicit, and a while (1) { yield(); } hints developers at the fact that they (a) won't exit automatically and (b) can employ other techniques for forcing an exit such as a global app timeout etc.
I think libtock-js would reasonably wait until there is no more work to be done, but I think libtock-c should exit when main returns.
@lschuermann I agree with you point of view.
I am still in favour of keeping the current behaviour and asking developers to explicitly use the exit system call if they want to exit. The idea behind this is that microcontroller apps usually do not exit and I think there will be more apps that use the while (1) than apps that will manually exit. I might be wrong 😃.
My basis for disagreeing is along the same lines as Alexandru. The default behavior for the majority of embedded apps is to never exit, so that makes sense to me as the default when returning from main.
That being said, Tock has many other aspects which follow standard OS behavior rather than embedded-specific. And the standard C premise is that when main returns, your program is done. So I can definitely see the rationale for what Brad is proposing.
What would you expect this program to do?
int main (void) {
return 1;
}
I think it would be surprising to not get the return code in the kernel.
I am in favor of finding all of the apps that rely on a yield forever loop and adding the loop in at the end of main. That makes it explicit that they intend to run forever.
What would you expect this program to do?
int main (void) { return 1; }I think it would be surprising to not get the return code in the kernel.
I have to admit that you have a point. I was under the impression that main was declared with a void return type as its return value is never used.
Wow I didn't expect this to get so many approvals so quickly. I only went through examples/ not tests/, so there are still probably a lot of broken apps.
Summarizing the discussion from the call:
A few people showed surprise at current behavior when first encountering it. Discussion on whether to match POSIX main() semantics, or go our own way. Tock is different than POSIX, so it can have different exit semantics when returning from main. Not uncommon for embedded systems to do their own thing on exiting from main(). Also, in many cases, exiting from main doesn't make sense. Question of what to do on exit-- kill application? Restart? What should be the default?
An alternative to use different functions, e.g.
mainandsetupthat have different semantics, at the expense of there being multiple possible entry points. Still doesn't answer the question of what should happen on termination (exit? restart?)
Personally, I'm biased towards having returning from main() either terminate or just prevent an application from receiving callbacks, but only because that's more in line with other systems I've worked with (and Tock has no obligation to be like other systems). It would be nice to be able to reclaim memory for boards with next to no RAM, but this is arguably atypical for an embedded system, at least in part because it complicates process memory handling and management.
In general, I think all of the alternatives (including this PR) we discussed in the meeting sounded workable, so long as they're documented somewhere. The documentation would take away the surprise some of us found when writing C applications with libtock-c (well, if we read said docs :smile:).
I added a commit that sets today-unused, would-be argc and argv to 0 and NULL. This isn't really a functional change of any kind, but the quick test I ran that motivated it was this:
diff --git a/examples/blink/main.c b/examples/blink/main.c
index b72ab6f..ebc15c9 100644
--- a/examples/blink/main.c
+++ b/examples/blink/main.c
@@ -1,12 +1,14 @@
#include <led.h>
#include <timer.h>
-int main(void) {
+int main(int argc, char** argv) {
// Ask the kernel how many LEDs are on this board.
int num_leds;
int err = led_count(&num_leds);
if (err < 0) return err;
+ if (argc) printf("%s\n", argv[0]);
+
Modifying existing apps to use the argc/argv signature and their values complies without warning or error. I suspect that's because this definition of main is indeed part of the C standard, not POSIX: https://stackoverflow.com/questions/2108192/what-are-the-valid-signatures-for-cs-main-function, and we are not compiling with -ffreestanding as Tock is a form of hosted environment.
While use is unlikely, I think explicitly zero-ing these parameters is a good safety rail to add until the day we set up something more explicit.
Ok! I came back to this. I went through all of the examples and added while(1) yield(); loops where needed.
This is breaking the library API semantics in a pretty major way, so I propose we quickly talk about how this would interact with any release schedules on the call on Friday.
+1
On Jan 25, 2024, at 05:18, Leon Schuermann @.***> wrote: This is breaking the library API semantics in a pretty major way, so I propose we quickly talk about how this would interact with any release schedules on the call on Friday.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you commented.Message ID: @.***>
Discussion from today's call:
- we have not, in the past, made any particular stability guarantees on libtock-c's API
- the versioning scheme is just matching the kernel's, to indicate it has been tested with this particular kernel version
- this should go into the release notes
@bradjc I can't see immediately why this is / was marked blocked. if it's no longer, feel free to remove. Marking last-call.
This passes CI finally!
Last call?