I am trying to work around this std::thread bug, which causes the stack to be unwound in an older version of gcc, by applying the noexcept
annotation:
#include <functional>
#include <stdexcept>
void thrower() {
throw std::runtime_error("A message is useful but where's my stack?");
}
void run() {
// Culprit:
auto j = std::function<void()>([] { thrower(); });
// Works fine:
// auto j = [] { thrower(); };
j();
}
void wrap() noexcept {
run();
// Works fine too:
// auto j = std::function<void()>([] { thrower(); });
// j();
}
int main() {
wrap();
}
Since the run
function is in a library, I need to apply noexcept
to a wrapper function, wrap
. However, I observe that when the run
function uses std::function
, the stack is still partly unwound and I don't see the crucial bit telling me that the exception originated in the thrower
:
#0 0x000000000041fb41 in raise ()
#1 0x0000000000401b30 in abort ()
#2 0x00000000004012cb in __gnu_cxx::__verbose_terminate_handler() [clone .cold] ()
#3 0x0000000000403876 in __cxxabiv1::__terminate(void (*)()) ()
#4 0x0000000000414d69 in __cxa_call_terminate ()
#5 0x0000000000403531 in __gxx_personality_v0 ()
#6 0x0000000000416fbe in _Unwind_RaiseException_Phase2 ()
#7 0x0000000000417ab6 in _Unwind_Resume ()
#8 0x0000000000402668 in std::function<void ()>::~function() (this=<optimized out>, __in_chrg=<optimized out>)
at /usr/include/c++/10/bits/std_function.h:303
#9 run () at main.cpp:10
#10 0x0000000000402671 in wrap () at main.cpp:17
#11 0x000000000040267f in main () at main.cpp:24
Note the _Unwind_Resume
in frame 7 and std::function
destructor in frame 8 (the latter I also saw this with gcc 7 but with gcc 10 I had to set optimization level to debug to see it).
What about std::function
is causing the stack to be unwound and why is this not happening with a lambda? Is this allowed by the standard? I don't know much about the rules around unwinding, so please refer to me to relevant resources where you can.
What can I do to prevent the stack unwinding, assuming that I can't modify run
?
More experiments:
If I use a lambda instead, I do see the thrower
and instead of _Unwind_Resume
there is _Unwind_RaiseException
:
... (as above) ...
#6 0x0000000000416e5e in _Unwind_RaiseException_Phase2 ()
#7 0x0000000000417630 in _Unwind_RaiseException ()
#8 0x00000000004036c8 in __cxa_throw ()
#9 0x0000000000402575 in thrower () at main.cpp:5
#10 0x0000000000402591 in operator() (__closure=<synthetic pointer>) at main.cpp:13
#11 run () at main.cpp:13
#12 0x000000000040259a in wrap () at main.cpp:17
#13 0x00000000004025a3 in main () at main.cpp:24
The stack is also complete if I copy the body of run
into wrap
, so the issue seems to be specifically about an exception thrown from the invocation of a std::function
escaping a function not annotated with noexcept
.
... (as above) ...
#11 std::__invoke_impl<void, wrap()::<lambda()>&> (__f=...) at /usr/include/c++/10/bits/invoke.h:60
#12 std::__invoke_r<void, wrap()::<lambda()>&> (__fn=...) at /usr/include/c++/10/bits/invoke.h:153
#13 std::_Function_handler<void(), wrap()::<lambda()> >::_M_invoke(const std::_Any_data &) (__functor=...)
at /usr/include/c++/10/bits/std_function.h:291
#14 0x0000000000402680 in std::function<void ()>::operator()() const (this=this@entry=0x7fffffffd780)
at /usr/include/c++/10/bits/std_function.h:248
#15 0x0000000000402641 in wrap () at main.cpp:20
#16 0x0000000000402667 in main () at main.cpp:24