2111. Which unexpected/terminate handler is called from the exception handling runtime?

Section: 21.8.4.4 [terminate], 99 [unexpected] Status: C++17 Submitter: Howard Hinnant Opened: 2011-12-06 Last modified: 2017-07-30

Priority: 3

View all other issues in [terminate].

View all issues with C++17 status.

Discussion:

Prior to N3242, modified by N3189, we said this about unexpected():

Effects: Calls the unexpected_handler function in effect immediately after evaluating the throw-expression (D.13.1), if called by the implementation, or calls the current unexpected_handler, if called by the program.

and this about terminate():

Effects: Calls the terminate_handler function in effect immediately after evaluating the throw-expression (18.8.3.1), if called by the implementation, or calls the current terminate_handler function, if called by the program.

But now in both places we say:

Calls the current unexpected_handler function.

and:

Calls the current terminate function.

The difference is that in C++98/03 if a destructor reset a handler during stack unwinding, that new handler was not called if the unwinding later led to unexpected() or terminate() being called. But these new words say that this new handler is called. This is an ABI-breaking change in the way exceptions are handled. Was this change intentional?

N3189 was mainly about introducing exception safety and getters for the handlers. I don't recall the issue of which handler gets called being part of the discussion.

I propose that we revert to the C++98/03 behavior in this regard, lest ABI's such as the Itanium ABI are invalidated. A mechanical way to do this is to revert bullets 9 and 12 of N3189.

[2011-12-09: Daniel comments]

There was no such semantic change intended. It was an unfortunate side effect when trying to better separate different responsibilities in the previous wording.

A related issue is 2088.

[2012-01-30: Howard comments]

The C++98/03 wording is somewhat ambiguous:

Calls the terminate_handler function in effect immediately after evaluating the throw-expression...

There are potentially two throw-expressions being referred to here, and it is not clear if this sentence is referring to just the first or both:

  1. throw assignment-expression;
  2. throw;

There is ample evidence in current implementations that it is understood that only 1. was meant. But clearly both 1 and 2 could have been meant. We need a clarification. Does an execution of a rethrow (throw;) update which handlers can potentially be called?

  1. throw; // update handlers to get_xxx()?

My opinion: Go with existing practice, and clarify what that practice is, if surveys find that everyone does the same thing. Gcc 4.2 and Apple do 1. only, and do not reset the handlers to the current handlers on throw;.

If current practice is not unanimously one way or the other, I have no strong opinion. I have not found a motivating use case for the use of any particular handler. Most applications set the handlers once at the beginning of the program and then do not change them, and so will not be impacted by whatever decision is made here.

[2014-02-15 Issaquah: Move to Review]

STL: Original change in N3242 came from trying to make set/get exception handler thread safe. The issue requests we revert to 98/03, which Howard notes was already ambiguous.

Alisdair: Issue author thinks we made this change in C++11 without taking into account Itanium ABI, which cannot implement the new semantic (without breaking compatibility).

Alisdair: original change in N3242 was trying to solve the problem of which handler is called when the handler is changing in another thread, but this turns out to be an issue in even the single-threaded case.

Pablo: despite wanting to make it thread safe, you are still changing a global

STL and Marshall confirm that there is real implementation divergance on the question, so we cannot pick just one behavior if we want to avoid breaking exisitng practice.

Alisdair: not sure who to talk to across all library vendors to fix, need more information for progress (IBM and Sun)

STL: Howard did identify a problem with the wording as well: throw; is a throw expression, but we typically want to re-activate the in-flight exception, not throw a new copy.

Pablo: wondering why all of this wording is here (N3189)? It looks like we were trying to handle another thread changing handler between a throw and terminate in current thread.

Alisdair: Anything working with exception handling should have used only thread-local resources, but that ship has sailed. We must account for the same exception object being re-thrown in multiple threads simultaneously, with no happens-before relationships.

Room: Why on earth would we care about exactly which way the program dies when the terminate calls are racing?!

Pablo: Reasonable to set the handler once (in main) and never change it.

Pablo: If willing to put lots of work into this, you could say at point of a throw these handlers become thread local but that is overkill. We want destructors to be able to change these handlers (if only for backwards compatibility).

Alisdair: the "do it right" is to do something per thread, but that is more work than vendors will want to do. Want to say setting handler while running multiple threads is unspecified.

Pablo: possible all we need to do is say it is always the current handler

STL: That prevents an implementation or single threaded program from calling a new handler after a throw, probably should say if terminate is called by the implementation (during EH), any handler that was current can be called. Leaves it up in the air as to when the handler is captured, supporting the diverging existing practices.

Jeffrey: use happens before terminology to avoid introducing races

STL: Give this to concurrency?

Jeffrey: It is in clause 18, generally LWG and not SG1 territory.

Alisdair: Concerned about introducing happens before into fundamental exception handling since it would affect single threaded performance as well. Want to give to concurrency or LEWG/EWG, we are into language design here.

Jeffrey: suspect LEWG won't have a strong opinion. I don't want it to be ours!!!

Pablo: Might be a case for core>

Alisdair: Would be happier if at least one core person were in the discussion.

STL: No sympathy for code that tries to guard the terminate handler.

Alisdair: We are back to set it once, globally. Want to be clear that if set_terminate is called just once, when EH is not active, and never changed again, then the user should get the handler from that specific call.

AlisdairM: "unspecified which handler is called if an exception is active when set_terminate is called." This supports existing behaviors, and guarantees which handler is called in non-conentious situations. Implicit assumption that a funtion becomes a handler only after a successful call to set_handler, so we are not leaving a door open to the implementation inventing entirely new handlers of its own.

Consensus.

Poll to confirm status as P1: new consensus is P3

Action: Alisdair provides new wording. Drop from P1 to P3, and move to Review.

[2015-05, Lenexa]

HH: we accidentally changed semantics of which handler gets called during exception unwinding. This was attempt to put it back. Discovered implementations don't really do anything. […] Fine with unspecified behavior to move this week.
STL/MC: observed different behavior
STL: legitimizes all implementations and tells users to not do this
Move to ready? 9/0/1

Proposed resolution:

Amend 21.8.4.4 [terminate] as indicated:

[[noreturn]] void terminate() noexcept;

Remarks: Called by the implementation when exception handling must be abandoned for any of several reasons (15.5.1) , in effect immediately after throwing the exception. May also be called directly by the program.

Effects: Calls a terminate_handler function. It is unspecified which terminate_handler function will be called if an exception is active during a call to set_terminate. Otherwise cCalls the current terminate_handler function. [Note: A default terminate_handler is always considered a callable handler in this context. — end note]

Amend 99 [unexpected] as indicated:

[[noreturn]] void unexpected();

Remarks: Called by the implementation when a function exits via an exception not allowed by its exception-specification (15.5.2), in effect after evaluating the throw-expression (D.11.1). May also be called directly by the program.

Effects: Calls an unexpected_handler function. It is unspecified which unexpected_handler function will be called if an exception is active during a call to set_unexpected. Otherwise cCalls the current unexpected_handler function. [Note: A default unexpected_handler is always considered a callable handler in this context. — end note]