Commit 55a6731a authored by Kenton Varda's avatar Kenton Varda

Make printStackTraceOnCrash() also register an std::terminate handler.

For whatever reason, the default termination handler on my machine is no longer printing the exception's `what()` string. It just aborts. That makes debugging hard.

This also means that we can now use `noexcept` in unit tests as a way to make uncaught exceptions abort the process _without_ unwinding, which is especially useful in tests that create threads since they often deadlock during unwind waiting for the thread to finish.
parent 114e20cc
...@@ -842,8 +842,10 @@ KJ_TEST("synchonous simple cross-thread events") { ...@@ -842,8 +842,10 @@ KJ_TEST("synchonous simple cross-thread events") {
Own<PromiseFulfiller<uint>> fulfiller; // accessed only from the subthread Own<PromiseFulfiller<uint>> fulfiller; // accessed only from the subthread
thread_local bool isChild = false; // to assert which thread we're in thread_local bool isChild = false; // to assert which thread we're in
Thread thread([&]() { // We use `noexcept` so that any uncaught exceptions immediately terminate the process without
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { // unwinding. Otherwise, the unwind would likely deadlock waiting for some synchronization with
// the other thread.
Thread thread([&]() noexcept {
isChild = true; isChild = true;
EventLoop loop; EventLoop loop;
...@@ -858,15 +860,9 @@ KJ_TEST("synchonous simple cross-thread events") { ...@@ -858,15 +860,9 @@ KJ_TEST("synchonous simple cross-thread events") {
// Wait until parent thread sets executor to null, as a way to tell us to quit. // Wait until parent thread sets executor to null, as a way to tell us to quit.
executor.lockExclusive().wait([](auto& val) { return val == nullptr; }); executor.lockExclusive().wait([](auto& val) { return val == nullptr; });
})) {
// Log here because it's likely the parent thread will never join and we'll hang forever
// without propagating the exception.
KJ_LOG(ERROR, *exception);
kj::throwRecoverableException(kj::mv(*exception));
}
}); });
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { ([&]() noexcept {
const Executor* exec; const Executor* exec;
{ {
auto lock = executor.lockExclusive(); auto lock = executor.lockExclusive();
...@@ -889,10 +885,7 @@ KJ_TEST("synchonous simple cross-thread events") { ...@@ -889,10 +885,7 @@ KJ_TEST("synchonous simple cross-thread events") {
KJ_EXPECT(i == 456); KJ_EXPECT(i == 456);
*executor.lockExclusive() = nullptr; *executor.lockExclusive() = nullptr;
})) { })();
// Log here because the thread join is likely to hang forever...
KJ_FAIL_EXPECT(*exception);
}
} }
KJ_TEST("asynchonous simple cross-thread events") { KJ_TEST("asynchonous simple cross-thread events") {
...@@ -900,8 +893,10 @@ KJ_TEST("asynchonous simple cross-thread events") { ...@@ -900,8 +893,10 @@ KJ_TEST("asynchonous simple cross-thread events") {
Own<PromiseFulfiller<uint>> fulfiller; // accessed only from the subthread Own<PromiseFulfiller<uint>> fulfiller; // accessed only from the subthread
thread_local bool isChild = false; // to assert which thread we're in thread_local bool isChild = false; // to assert which thread we're in
Thread thread([&]() { // We use `noexcept` so that any uncaught exceptions immediately terminate the process without
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { // unwinding. Otherwise, the unwind would likely deadlock waiting for some synchronization with
// the other thread.
Thread thread([&]() noexcept {
isChild = true; isChild = true;
EventLoop loop; EventLoop loop;
...@@ -916,18 +911,12 @@ KJ_TEST("asynchonous simple cross-thread events") { ...@@ -916,18 +911,12 @@ KJ_TEST("asynchonous simple cross-thread events") {
// Wait until parent thread sets executor to null, as a way to tell us to quit. // Wait until parent thread sets executor to null, as a way to tell us to quit.
executor.lockExclusive().wait([](auto& val) { return val == nullptr; }); executor.lockExclusive().wait([](auto& val) { return val == nullptr; });
})) {
// Log here because it's likely the parent thread will never join and we'll hang forever
// without propagating the exception.
KJ_LOG(ERROR, *exception);
kj::throwRecoverableException(kj::mv(*exception));
}
}); });
([&]() noexcept {
EventLoop loop; EventLoop loop;
WaitScope waitScope(loop); WaitScope waitScope(loop);
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
const Executor* exec; const Executor* exec;
{ {
auto lock = executor.lockExclusive(); auto lock = executor.lockExclusive();
...@@ -950,10 +939,7 @@ KJ_TEST("asynchonous simple cross-thread events") { ...@@ -950,10 +939,7 @@ KJ_TEST("asynchonous simple cross-thread events") {
KJ_EXPECT(promise.wait(waitScope) == 456); KJ_EXPECT(promise.wait(waitScope) == 456);
*executor.lockExclusive() = nullptr; *executor.lockExclusive() = nullptr;
})) { })();
// Log here because the thread join is likely to hang forever...
KJ_FAIL_EXPECT(*exception);
}
} }
KJ_TEST("synchonous promise cross-thread events") { KJ_TEST("synchonous promise cross-thread events") {
...@@ -962,8 +948,10 @@ KJ_TEST("synchonous promise cross-thread events") { ...@@ -962,8 +948,10 @@ KJ_TEST("synchonous promise cross-thread events") {
Promise<uint> promise = nullptr; // accessed only from the subthread Promise<uint> promise = nullptr; // accessed only from the subthread
thread_local bool isChild = false; // to assert which thread we're in thread_local bool isChild = false; // to assert which thread we're in
Thread thread([&]() { // We use `noexcept` so that any uncaught exceptions immediately terminate the process without
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { // unwinding. Otherwise, the unwind would likely deadlock waiting for some synchronization with
// the other thread.
Thread thread([&]() noexcept {
isChild = true; isChild = true;
EventLoop loop; EventLoop loop;
...@@ -986,15 +974,9 @@ KJ_TEST("synchonous promise cross-thread events") { ...@@ -986,15 +974,9 @@ KJ_TEST("synchonous promise cross-thread events") {
// Wait until parent thread sets executor to null, as a way to tell us to quit. // Wait until parent thread sets executor to null, as a way to tell us to quit.
executor.lockExclusive().wait([](auto& val) { return val == nullptr; }); executor.lockExclusive().wait([](auto& val) { return val == nullptr; });
})) {
// Log here because it's likely the parent thread will never join and we'll hang forever
// without propagating the exception.
KJ_LOG(ERROR, *exception);
kj::throwRecoverableException(kj::mv(*exception));
}
}); });
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { ([&]() noexcept {
const Executor* exec; const Executor* exec;
{ {
auto lock = executor.lockExclusive(); auto lock = executor.lockExclusive();
...@@ -1017,10 +999,7 @@ KJ_TEST("synchonous promise cross-thread events") { ...@@ -1017,10 +999,7 @@ KJ_TEST("synchonous promise cross-thread events") {
KJ_EXPECT(i == 321); KJ_EXPECT(i == 321);
*executor.lockExclusive() = nullptr; *executor.lockExclusive() = nullptr;
})) { })();
// Log here because the thread join is likely to hang forever...
KJ_FAIL_EXPECT(*exception);
}
} }
KJ_TEST("asynchonous promise cross-thread events") { KJ_TEST("asynchonous promise cross-thread events") {
...@@ -1029,8 +1008,10 @@ KJ_TEST("asynchonous promise cross-thread events") { ...@@ -1029,8 +1008,10 @@ KJ_TEST("asynchonous promise cross-thread events") {
Promise<uint> promise = nullptr; // accessed only from the subthread Promise<uint> promise = nullptr; // accessed only from the subthread
thread_local bool isChild = false; // to assert which thread we're in thread_local bool isChild = false; // to assert which thread we're in
Thread thread([&]() { // We use `noexcept` so that any uncaught exceptions immediately terminate the process without
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { // unwinding. Otherwise, the unwind would likely deadlock waiting for some synchronization with
// the other thread.
Thread thread([&]() noexcept {
isChild = true; isChild = true;
EventLoop loop; EventLoop loop;
...@@ -1053,18 +1034,12 @@ KJ_TEST("asynchonous promise cross-thread events") { ...@@ -1053,18 +1034,12 @@ KJ_TEST("asynchonous promise cross-thread events") {
// Wait until parent thread sets executor to null, as a way to tell us to quit. // Wait until parent thread sets executor to null, as a way to tell us to quit.
executor.lockExclusive().wait([](auto& val) { return val == nullptr; }); executor.lockExclusive().wait([](auto& val) { return val == nullptr; });
})) {
// Log here because it's likely the parent thread will never join and we'll hang forever
// without propagating the exception.
KJ_LOG(ERROR, *exception);
kj::throwRecoverableException(kj::mv(*exception));
}
}); });
([&]() noexcept {
EventLoop loop; EventLoop loop;
WaitScope waitScope(loop); WaitScope waitScope(loop);
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
const Executor* exec; const Executor* exec;
{ {
auto lock = executor.lockExclusive(); auto lock = executor.lockExclusive();
...@@ -1087,17 +1062,16 @@ KJ_TEST("asynchonous promise cross-thread events") { ...@@ -1087,17 +1062,16 @@ KJ_TEST("asynchonous promise cross-thread events") {
KJ_EXPECT(promise2.wait(waitScope) == 321); KJ_EXPECT(promise2.wait(waitScope) == 321);
*executor.lockExclusive() = nullptr; *executor.lockExclusive() = nullptr;
})) { })();
// Log here because the thread join is likely to hang forever...
KJ_FAIL_EXPECT(*exception);
}
} }
KJ_TEST("cancel cross-thread event before it runs") { KJ_TEST("cancel cross-thread event before it runs") {
MutexGuarded<kj::Maybe<const Executor&>> executor; // to get the Executor from the other thread MutexGuarded<kj::Maybe<const Executor&>> executor; // to get the Executor from the other thread
Thread thread([&]() { // We use `noexcept` so that any uncaught exceptions immediately terminate the process without
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { // unwinding. Otherwise, the unwind would likely deadlock waiting for some synchronization with
// the other thread.
Thread thread([&]() noexcept {
EventLoop loop; EventLoop loop;
WaitScope waitScope(loop); WaitScope waitScope(loop);
...@@ -1107,18 +1081,12 @@ KJ_TEST("cancel cross-thread event before it runs") { ...@@ -1107,18 +1081,12 @@ KJ_TEST("cancel cross-thread event before it runs") {
// Wait until parent thread sets executor to null, as a way to tell us to quit. // Wait until parent thread sets executor to null, as a way to tell us to quit.
executor.lockExclusive().wait([](auto& val) { return val == nullptr; }); executor.lockExclusive().wait([](auto& val) { return val == nullptr; });
})) {
// Log here because it's likely the parent thread will never join and we'll hang forever
// without propagating the exception.
KJ_LOG(ERROR, *exception);
kj::throwRecoverableException(kj::mv(*exception));
}
}); });
([&]() noexcept {
EventLoop loop; EventLoop loop;
WaitScope waitScope(loop); WaitScope waitScope(loop);
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
const Executor* exec; const Executor* exec;
{ {
auto lock = executor.lockExclusive(); auto lock = executor.lockExclusive();
...@@ -1133,18 +1101,17 @@ KJ_TEST("cancel cross-thread event before it runs") { ...@@ -1133,18 +1101,17 @@ KJ_TEST("cancel cross-thread event before it runs") {
} }
*executor.lockExclusive() = nullptr; *executor.lockExclusive() = nullptr;
})) { })();
// Log here because the thread join is likely to hang forever...
KJ_FAIL_EXPECT(*exception);
}
} }
KJ_TEST("cancel cross-thread event while it runs") { KJ_TEST("cancel cross-thread event while it runs") {
MutexGuarded<kj::Maybe<const Executor&>> executor; // to get the Executor from the other thread MutexGuarded<kj::Maybe<const Executor&>> executor; // to get the Executor from the other thread
Own<PromiseFulfiller<void>> fulfiller; // accessed only from the subthread Own<PromiseFulfiller<void>> fulfiller; // accessed only from the subthread
Thread thread([&]() { // We use `noexcept` so that any uncaught exceptions immediately terminate the process without
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { // unwinding. Otherwise, the unwind would likely deadlock waiting for some synchronization with
// the other thread.
Thread thread([&]() noexcept {
EventLoop loop; EventLoop loop;
WaitScope waitScope(loop); WaitScope waitScope(loop);
...@@ -1157,18 +1124,12 @@ KJ_TEST("cancel cross-thread event while it runs") { ...@@ -1157,18 +1124,12 @@ KJ_TEST("cancel cross-thread event while it runs") {
// Wait until parent thread sets executor to null, as a way to tell us to quit. // Wait until parent thread sets executor to null, as a way to tell us to quit.
executor.lockExclusive().wait([](auto& val) { return val == nullptr; }); executor.lockExclusive().wait([](auto& val) { return val == nullptr; });
})) {
// Log here because it's likely the parent thread will never join and we'll hang forever
// without propagating the exception.
KJ_LOG(ERROR, *exception);
kj::throwRecoverableException(kj::mv(*exception));
}
}); });
([&]() noexcept {
EventLoop loop; EventLoop loop;
WaitScope waitScope(loop); WaitScope waitScope(loop);
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
const Executor* exec; const Executor* exec;
{ {
auto lock = executor.lockExclusive(); auto lock = executor.lockExclusive();
...@@ -1187,10 +1148,7 @@ KJ_TEST("cancel cross-thread event while it runs") { ...@@ -1187,10 +1148,7 @@ KJ_TEST("cancel cross-thread event while it runs") {
exec->executeSync([&]() { fulfiller->fulfill(); }); exec->executeSync([&]() { fulfiller->fulfill(); });
*executor.lockExclusive() = nullptr; *executor.lockExclusive() = nullptr;
})) { })();
// Log here because the thread join is likely to hang forever...
KJ_FAIL_EXPECT(*exception);
}
} }
} // namespace } // namespace
......
...@@ -378,6 +378,43 @@ String getStackTrace() { ...@@ -378,6 +378,43 @@ String getStackTrace() {
return kj::str(stringifyStackTraceAddresses(trace), stringifyStackTrace(trace)); return kj::str(stringifyStackTraceAddresses(trace), stringifyStackTrace(trace));
} }
namespace {
void terminateHandler() {
void* traceSpace[32];
// ignoreCount = 3 to ignore std::terminate entry.
auto trace = kj::getStackTrace(traceSpace, 3);
kj::String message;
auto eptr = std::current_exception();
if (eptr != nullptr) {
try {
std::rethrow_exception(eptr);
} catch (const kj::Exception& exception) {
message = kj::str("*** Fatal uncaught kj::Exception: ", exception, '\n');
} catch (const std::exception& exception) {
message = kj::str("*** Fatal uncaught std::exception: ", exception.what(),
"\nstack: ", stringifyStackTraceAddresses(trace),
stringifyStackTrace(trace), '\n');
} catch (...) {
message = kj::str("*** Fatal uncaught exception of type: ", kj::getCaughtExceptionType(),
"\nstack: ", stringifyStackTraceAddresses(trace),
stringifyStackTrace(trace), '\n');
}
} else {
message = kj::str("*** std::terminate() called with no exception"
"\nstack: ", stringifyStackTraceAddresses(trace),
stringifyStackTrace(trace), '\n');
}
kj::FdOutputStream(STDERR_FILENO).write(message.begin(), message.size());
_exit(1);
}
} // namespace
#if _WIN32 && _M_X64 #if _WIN32 && _M_X64
namespace { namespace {
...@@ -461,6 +498,9 @@ void printStackTraceOnCrash() { ...@@ -461,6 +498,9 @@ void printStackTraceOnCrash() {
mainThreadId = GetCurrentThreadId(); mainThreadId = GetCurrentThreadId();
KJ_WIN32(SetConsoleCtrlHandler(breakHandler, TRUE)); KJ_WIN32(SetConsoleCtrlHandler(breakHandler, TRUE));
SetUnhandledExceptionFilter(&sehHandler); SetUnhandledExceptionFilter(&sehHandler);
// Also override std::terminate() handler with something nicer for KJ.
std::set_terminate(&terminateHandler);
} }
#elif KJ_HAS_BACKTRACE #elif KJ_HAS_BACKTRACE
...@@ -523,9 +563,13 @@ void printStackTraceOnCrash() { ...@@ -523,9 +563,13 @@ void printStackTraceOnCrash() {
// because stack traces on ctrl+c can be obnoxious for, say, command-line tools. // because stack traces on ctrl+c can be obnoxious for, say, command-line tools.
KJ_SYSCALL(sigaction(SIGINT, &action, nullptr)); KJ_SYSCALL(sigaction(SIGINT, &action, nullptr));
#endif #endif
// Also override std::terminate() handler with something nicer for KJ.
std::set_terminate(&terminateHandler);
} }
#else #else
void printStackTraceOnCrash() { void printStackTraceOnCrash() {
std::set_terminate(&terminateHandler);
} }
#endif #endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment