From e23bed5e646d84b4d327fa6dda7805e181be9d69 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 15 Jun 2025 11:41:49 +0200 Subject: [PATCH] libutil: expose the functionality of Lix's exception handler This introduces three new things: * `handleException` which prints out exception details and its stack trace. * `handleExceptionWithAsyncTrace` which does the same, but also prints the async trace if any. * `LIX_BLOCK_ON` which is awaits a promise and adds an exception trace if an exception got thrown, similar to `LIX_TRY_AWAIT`. However, this is not supposed to be used in async functions, but on callsites of `aio.blockOn()` which is especially useful for Hydra[1]. For `LIX_BLOCK_ON` I had to introduce another function because there's apparently no way to implement all of it in a macro: on macros with compound statements the return value must be a trivial expression at the bottom, i.e. no `try`/`catch`. Now, returning the value from the `try`-block requires the variable to be defined up-front, but for that we'd need to know the type-name. Hence the construction with a template-function being invoked by a macro that injects the current source-location. [1] https://git.lix.systems/lix-project/hydra/pulls/52 Change-Id: I56cc92c94f7e8f0be5d4dc5a7d8cb21a92e776ef --- lix/libmain/crash-handler.cc | 30 ++---------------------------- lix/libutil/async.hh | 15 ++++++++++++--- lix/libutil/error.cc | 31 +++++++++++++++++++++++++++++++ lix/libutil/error.hh | 2 ++ 4 files changed, 47 insertions(+), 31 deletions(-) diff --git a/lix/libmain/crash-handler.cc b/lix/libmain/crash-handler.cc index 419eb0f26..2b8fddd96 100644 --- a/lix/libmain/crash-handler.cc +++ b/lix/libmain/crash-handler.cc @@ -1,11 +1,9 @@ #include "lix/libmain/crash-handler.hh" #include "lix/libutil/error.hh" -#include "lix/libutil/fmt.hh" #include "lix/libutil/logging.hh" #include #include -#include namespace nix { @@ -23,38 +21,14 @@ void onTerminate() } else { logFatal("std::terminate() called without exception"); } - } catch (const ForeignException & ex) { - asyncTrace = ex.asyncTrace(); - logFatal(fmt("Exception: %s: %s", boost::core::demangle(ex.innerType.name()), ex.what())); } catch (const BaseException & ex) { - asyncTrace = ex.asyncTrace(); - logFatal(fmt("Exception: %s: %s", boost::core::demangle(typeid(ex).name()), ex.what())); + logException("Exception", ex); } catch (const std::exception & ex) { // NOLINT(lix-foreign-exceptions) - logFatal(fmt("Exception: %s: %s", boost::core::demangle(typeid(ex).name()), ex.what())); + logException("Exception", ex); } catch (...) { logFatal("Unknown exception! Spooky."); } - logFatal("Stack trace:"); - logFatal(getStackTrace()); - - if (asyncTrace && !asyncTrace->empty()) { - logFatal("Async task trace (probably incomplete):"); - for (auto [i, frame] : enumerate(*asyncTrace)) { - logFatal( - fmt("#%i: %s (%s:%i:%i)", - i, - frame.location.function_name(), - frame.location.file_name(), - frame.location.line(), - frame.location.column()) - ); - if (frame.description) { - logFatal(fmt("\t%s", *frame.description)); - } - } - } - std::abort(); } } diff --git a/lix/libutil/async.hh b/lix/libutil/async.hh index 288dfaeee..fa3a93ce8 100644 --- a/lix/libutil/async.hh +++ b/lix/libutil/async.hh @@ -44,7 +44,9 @@ struct AsyncIoRoot KJ_DISALLOW_COPY_AND_MOVE(AsyncIoRoot); template - auto blockOn(kj::Promise && promise); + auto blockOn( + kj::Promise && promise, std::source_location call_site = std::source_location::current() + ); }; inline AsyncContext & AIO() @@ -137,7 +139,14 @@ static constexpr std::optional lixAsyncTaskContext() #endif template -inline auto nix::AsyncIoRoot::blockOn(kj::Promise && promise) -{ +inline auto nix::AsyncIoRoot::blockOn(kj::Promise && promise, std::source_location call_site) +try { return detail::runAsyncUnwrap(promise.wait(kj.waitScope)); +} catch (BaseException & e) { + e.addAsyncTrace(call_site); + throw; +} catch (std::exception & e) { /* NOLINT(lix-foreign-exceptions) */ + ForeignException fe(e); + fe.addAsyncTrace(call_site); + throw fe; } diff --git a/lix/libutil/error.cc b/lix/libutil/error.cc index 4df606675..34e2ec98f 100644 --- a/lix/libutil/error.cc +++ b/lix/libutil/error.cc @@ -10,6 +10,8 @@ #include #include +#include + namespace nix { void BaseError::addTrace(std::shared_ptr && e, HintFmt hint) @@ -441,4 +443,33 @@ void ignoreExceptionExceptInterrupt(Verbosity lvl) } } +void logException(std::string_view message_prefix, const std::exception & ex) +{ + const ForeignException * cast_exc = dynamic_cast(&ex); + auto typeName = cast_exc ? cast_exc->innerType.name() : typeid(ex).name(); + logFatal(fmt("%s: %s: %s", message_prefix, boost::core::demangle(typeName), ex.what())); + logFatal("Stack trace:"); + logFatal(getStackTrace()); + + const BaseException * cast_exc_with_async_trace = dynamic_cast(&ex); + if (cast_exc_with_async_trace) { + auto asyncTrace = cast_exc_with_async_trace->asyncTrace(); + if (asyncTrace && !asyncTrace->empty()) { + logFatal("Async task trace (probably incomplete):"); + for (auto [i, frame] : enumerate(*asyncTrace)) { + logFatal( + fmt("#%i: %s (%s:%i:%i)", + i, + frame.location.function_name(), + frame.location.file_name(), + frame.location.line(), + frame.location.column()) + ); + if (frame.description) { + logFatal(fmt("\t%s", *frame.description)); + } + } + } + } +} } diff --git a/lix/libutil/error.hh b/lix/libutil/error.hh index 2530b0a82..d2c127ff2 100644 --- a/lix/libutil/error.hh +++ b/lix/libutil/error.hh @@ -328,4 +328,6 @@ void ignoreExceptionInDestructor(Verbosity lvl = lvlError); */ void ignoreExceptionExceptInterrupt(Verbosity lvl = lvlError); +/** Print out details about an exception and its stack trace. */ +void logException(std::string_view message_prefix, const std::exception & ex); }