Skip to content

Fix MPR7253, exit behaviour in case of raising at_exit functions.#685

Closed
dbuenzli wants to merge 1 commit intoocaml:trunkfrom
dbuenzli:mpr-7253-fix-raising-at-exit
Closed

Fix MPR7253, exit behaviour in case of raising at_exit functions.#685
dbuenzli wants to merge 1 commit intoocaml:trunkfrom
dbuenzli:mpr-7253-fix-raising-at-exit

Conversation

@dbuenzli
Copy link
Copy Markdown
Contributor

@dbuenzli dbuenzli commented Jul 12, 2016

Fixes MPR7253

Rather than building a closure that captures the list of [at_exit]
functions to call at [exit] we explicitly store them in a mutable
list in LIFO order. We change the [Pervasives.do_at_exit] function
so that it sequentially pops one from the list before executing it.

If the popped function raises, the general uncaught exception mecanism
of [caml_main] kicks in. This eventually leads to the execution of
[Printexc.handle_uncaught_exception'] which reports the uncaught
exception and then calls [Pervasives.do_at_exit] to continue calling
the remaining [at_exit] functions. The latter call is guarded to catch
and report further exception raised by [at_exit] function in a loop,
so that each uncaught expression in [at_exit] functions gets reported.

This changes the semantics of the handler given to
Printexc.set_uncaught_exception_handler. It used to be that handler
would be called after all [at_exit] functions would have been called.
This is no longer the case, it is called before any [at_exit] function
that didn't get executed yet when the uncaught exception was raised.

Rather than building a closure that captures the list of [at_exit]
functions to call at [exit] we explicitly store them in a mutable
list in LIFO order. We change the [Pervasives.do_at_exit] function
so that it sequentially pops one from the list before executing it.

If the popped function raises, the general uncaught exception mecanism
of [caml_main] kicks in. This eventually leads to the execution of
[Printexc.handle_uncaught_exception'] which reports the uncaught
exception and then calls [Pervasives.do_at_exit] to continue calling
the remaining [at_exit] functions. The latter call is guarded to catch
and report further exception raised by [at_exit] function in a loop,
so that each uncaught expression in [at_exit] functions gets reported.

This changes the semantics of the handler given to
Printexc.set_uncaught_exception_handler. It used to be that handler
would be called after all [at_exit] functions would have been called.
This is no longer the case, it is called before any [at_exit] function
that didn't get executed yet when the uncaught exception was raised.
print_raw_backtrace stderr raw_backtrace;
eprintf "Fatal error in uncaught exception handler: exception %s\n"
(to_string exn');
print_raw_backtrace stderr raw_backtrace';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

N.B. what you see here is a diff artefact I'm not changing the structure of this see the removed None case above.

@alainfrisch
Copy link
Copy Markdown
Contributor

Contrary to what I believed (and commented on the other PR), one can already have Pervasives.exit gives back control (through an exception), and this new PR does not change that. I'm wondering if this shouldn't be addressed as well. Couldn't one simply run the pending at_exit functions when exit is called (popping them one by one from the list ref before they are executed), and if a nested call to exit occurs, this would simply trigger a new local loop (at the top of the stack)? If any at_exit function raises an exception, it is displayed by calling the printer in Printexc, but this does not stop the loop.

(There could also be a global mechanism to remember about the first or last non-0 code passed to exit to support the case of nested exits.)

@dbuenzli
Copy link
Copy Markdown
Contributor Author

Contrary to what I believed (and commented on the other PR), one can already have Pervasives.exit gives back control (through an exception), and this new PR does not change that. I'm wondering if this shouldn't be addressed as well.

Indeed and somehow I agree exit should be a sink that never returns.

If any at_exit function raises an exception, it is displayed by calling the printer in Printexc, but this does not stop the loop.

The only problem is that we can't access Printexc's function in Pervasives due to the dependency chain. This is what this comment said and I tried to circumvent via this "solution".

Now I didn't dig too far into Printexc to see if this this can maybe be reversed (of course at the moment the call made by Printexc.handle_uncaught_exception to Pervasives.do_at_exit prevents this, but this may not be needed if we handle the loop in Pervasives).

Will dig further...

@dbuenzli dbuenzli closed this Jul 12, 2016
@alainfrisch
Copy link
Copy Markdown
Contributor

The "custom uncaught exception handlers" take a raw_backtrace argument, and the default one rely on backtrace functions from Printexc. It does not seem realistic to move all that to Pervasives.

But there is already a "for system use only" section in Pervasives (also exposing do_at_exit), and it could be ok to add a forward reference to Printexc there to break the cycle. (The code in Pervasives must behave correctly if Printexc is not linked in, though.)

Alternatively, one could collect all exceptions raised by at_exit hooks in a list ref exposed by Pervasives to the runtime system and let the runtime system call the uncaught exception printer registered by Printexc (although we would probably not get the stack trace in such cases).

@xavierleroy
Copy link
Copy Markdown
Contributor

The issue is still open (e.g. in the original Mantis report), so I'm reading this discussion again. I cannot get an idea of which semantics (singular or plural) @dbuenzli , @alainfrisch , and the rest of us expect for an uncaught exception raised out of an at_exit function. Not the semantics currently implemented, that's for sure, but which one(s) instead? Please describe the semantics in terms of behaviors, not in terms of implementation of at_exit. That will come later.

@dbuenzli
Copy link
Copy Markdown
Contributor Author

From the documentation bits of this PR I think we want this:

If the registered function raises an uncaught exception it is given to the handler setup by {!Printexc.set_uncaught_exception_handler}.

@dbuenzli
Copy link
Copy Markdown
Contributor Author

dbuenzli commented Oct 19, 2017

However, as noted, this changes the semantics of {!Printexc.set_uncaught_exception_handler} which somehow seems to have been designed under the assumption that it will be called with a single unhandled exception from the program. Not necessarily in fact since the comment doesn't say when it is given to the uncaught the exception handler.

@xavierleroy
Copy link
Copy Markdown
Contributor

If the registered function raises an uncaught exception it is given to the handler setup by {!Printexc.set_uncaught_exception_handler}.

This doesn't say what happens when the handler calls exit itself, although I guess you intend this to execute the remaining at_exit finalizers (those that haven't been executed yet), right? Still, I can't convince myself this is the obvious thing to do. Why not, say, ignore the exceptions raised by at_exit functions? (Maybe not a great idea, but just an example of some other behavior.)

@dbuenzli
Copy link
Copy Markdown
Contributor Author

dbuenzli commented Oct 19, 2017

This doesn't say what happens when the handler calls exit itself,

Ah ok you also wanted that story aswell. This is MPR7178 and discussed in GPR675 in particular for me this comment.

So to sum up the full story for me would be for an at_exit handler f:

  1. The registered function f is wrapped by at_exit with a catch all exception handler, if f raises an exception it is eventually passed to Printexc.set_uncaught_exception_handler
  2. If the registered function f calls exit, Invalid_argument is raised (and thus catched by the protecting catch all handler).
  3. Possibly, introduce a function exit_at_exit : int -> unit that allows to set the ret code in an at_exit handler with the semantics that the last exit_at_exit executed with non-zero exit code defines the retcode of the program.

@xavierleroy
Copy link
Copy Markdown
Contributor

Thanks a lot @dbuenzli for having summarized the whole story. I'm still unsure where we go from there: is your semantics the one to be pursued or shall we look for something else?

@dbuenzli
Copy link
Copy Markdown
Contributor Author

An easier one would be to have 1.+2. without the Printexc. business this would at least solve the issues in some way.

While I'm not so fond about systems who silently swallow errors as if nothing happened, one could argue that writing an exit handler requires care and thus mandate the programmer do the Printexc business herself in her fs --- which she perfectly can.

Regarding 3. the actual use cases (one is here) may be too rare to justify it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants