emscripten-core/emscripten

EXIT_RUNTIME=0 semantics

Closed this issue · 8 comments

juj commented

Currently src/settings.js states:

// If 0, the runtime is not quit when main() completes (allowing code to
// run afterwards, for example from the browser main event loop). atexit()s
// are also not executed, and we can avoid including code for runtime shutdown,
// like flushing the stdio streams.
// Set this to 1 if you do want atexit()s or stdio streams to be flushed
// on exit.
var EXIT_RUNTIME = false;

If user passes -sEXIT_RUNTIME=0, does that mean

a) the generated code will never shut down, and no disk size will be expended to implement/track shutdown, or
b) falling out of main() will not shut down the page, but yield with a live runtime. The user can shut down the runtime either via explicit call to exit/emscripten_force_exit/dropping runtime keepalive refcount to zero.

In -sMINIMAL_RUNTIME, -sEXIT_RUNTIME=0 crystal clear means a). In regular runtime, it originally started out as meaning b), though it seems like the multitude of #if EXIT_RUNTIMEs in the src/lib*.js code seem to have kind of taken it to a direction of a) as well.

I wonder if things would be simpler if there were separate flags for these two usages?

Maybe something like -sEXIT_RUNTIME=disabled to mean that the concept of runtime shutting down doesn't need to be emitted to the web page (and falling off of main() should always yield), and -sEXIT_RUNTIME=yield to mean that falling out of main() should yield to the event loop, rather than shut down - and the ability to shut down later would be generated to the page?

One thing that makes things a bit more complex here is that in -pthread builds, the pthreads will need to have the ability to shut down at least enough of its runtime, so that the Worker hosting a pthread can acquire a different pthread to host later on.

My understanding has always been (a). i.e: If you build with -sNO_EXIT_RUNTIME you won't ever get your global destructors called because we remove all the of the atexit mechanisms and such (i.e. its mostly a code saving tool).

If there are places in the where where it seems that EXIT_RUNTIME is doing something different it could be where it overlaps with some other feature (e.g. threads) making it hard to fully to (a) in as much as might want, but (a) is still the intended meaning (I believe). Can you point to the places in the code where you think we are doing it wrong?

This is one of the most bug-generating settings that we have an it has caused a lot headaches over the years. I would be loath to make it even more complex by adding yet another setting here.

Also I'm not sure why would want to encode (b) in the settings since it seems like we have several existing way to exit main without exiting the runtime and then exit later. (Am I misunderstanding or is (b) just the same as writing emscripten_runtime_keepalive_push() at the end of your main function?)

juj commented

My understanding has always been (a). i.e: If you build with -sNO_EXIT_RUNTIME you won't ever get your global destructors called because we remove all the of the atexit mechanisms and such (i.e. its mostly a code saving tool).

Originally -sNO_EXIT_RUNTIME defined only what the default behavior of falling off of main() should be (99634e2#diff-f6437b5f2e4d199e7a66f03c9d33dc5e06f5d2edc29abc401417288a43633793R51-R52).

I.e. originally, passing -sNO_EXIT_RUNTIME was indeed identical to building with -sEXIT_RUNTIME today and having emscripten_exit_with_live_runtime() as the last line. So building with -sNO_EXIT_RUNTIME would mean that calling exit(0) would not quit, and one would have to call emscripten_force_exit(0) instead to shut down.

The semantics have shifted over to a), which is good to hear that it means that also for regular runtime. Posted https://github.com/juj/emscripten/pull/new/update_exit_runtime_docs to update documentation here.

juj commented

Btw can you think of any good reason why exit() still has this "soft" meaning, where calling exit() might not actually exit the program, if the keepalive refcount is greater than zero?

To me that does not seem nice. The C exit() should always forcibly exit, whether called from main thread or a pthread. Otherwise would be a divergence from the POSIX standards.

Originally a long time ago I think exit() behavior was made soft like that to ease porting of some programs that would call exit() at awkward times. Making exit() a no-op at such times would help avoid having to change the source code of those programs.

But that was a long time ago, when Emscripten was still in its infancy. Today, it feels to me that exit() should be stern, and always exit no matter what. Unless there is some relevant use case that really warrants no-opping it?

Btw can you think of any good reason why exit() still has this "soft" meaning, where calling exit() might not actually exit the program, if the keepalive refcount is greater than zero?

To me that does not seem nice. The C exit() should always forcibly exit, whether called from main thread or a pthread. Otherwise would be a divergence from the POSIX standards.

Originally a long time ago I think exit() behavior was made soft like that to ease porting of some programs that would call exit() at awkward times. Making exit() a no-op at such times would help avoid having to change the source code of those programs.

But that was a long time ago, when Emscripten was still in its infancy. Today, it feels to me that exit() should be stern, and always exit no matter what. Unless there is some relevant use case that really warrants no-opping it?

If seems like if we make that change then it would make emscripten_force_exit() redundant? Is that right? Essentially we would make exit() into emscripten_force_exit() and the runtime keepalive stuff would only be for soft exit?

That seems reasonable to me. I can't think any good use case for wanting exit() to mean soft exit.

sgtm

juj commented

Essentially we would make exit() into emscripten_force_exit()

Yeah, that's right.

Unless there is some super-odd reason still lingering around why exit() shouldn't really exit, I think it would be a fine time by now to make exit() really behave like emscripten_force_exit(), and make emscripten_force_exit() be an alias to the regular exit().

The only reason for exit() being soft like this that I recall is from a really long time ago when a lot of new software was ported to Emscripten, and wanting to make it all work without changing too many lines of the original codebase. Which, I think is no longer a thing given that Emscripten and WebAssembly is so much more established.

@kripken can you recall of a reason to still keep exit() from not actually exiting the runtime, if the keepalive refcount is >0?

IIRC the issue is that the web is weird with async events, and you sometimes needs to do emscripten_exit_with_live_runtime() etc. to keep the runtime alive after main finishes etc. It could be an error to have async events in flight and call exit, in other words - it would mean you forgot about them. Forcing exit is a way to say "I don't care if anything is in flight."

But I don't feel strongly here - it's possible this is not really a useful distinction. We'd need to be careful about a breaking change, though, as this runtime stuff can be annoying for users to debug if it suddenly breaks.

IIRC the issue is that the web is weird with async events, and you sometimes needs to do emscripten_exit_with_live_runtime() etc. to keep the runtime alive after main finishes etc. It could be an error to have async events in flight and call exit, in other words - it would mean you forgot about them. Forcing exit is a way to say "I don't care if anything is in flight."

I think the expectation of the C programmer is that nothing else will happen after exit(), so I think this should be a safe change.

The only user would would be effected would be those who want to use exit() to only unwind to the event loop, that seem really like a very unlikely/odd thing to want to do.