unexpected
/terminate
handler is called from the exception handling runtime?Section: 17.9.5.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 currentunexpected_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 currentterminate_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?
[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:
throw assignment-expression;
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?
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 17.9.5.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 whichterminate_handler
function will be called if an exception is active during a call toset_terminate
. Otherwise cCalls the currentterminate_handler
function. [Note: A defaultterminate_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 whichunexpected_handler
function will be called if an exception is active during a call toset_unexpected
. Otherwise cCalls the currentunexpected_handler
function. [Note: A defaultunexpected_handler
is always considered a callable handler in this context. — end note]