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
This commit is contained in:
@@ -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 <boost/core/demangle.hpp>
|
||||
#include <exception>
|
||||
#include <source_location>
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
+12
-3
@@ -44,7 +44,9 @@ struct AsyncIoRoot
|
||||
KJ_DISALLOW_COPY_AND_MOVE(AsyncIoRoot);
|
||||
|
||||
template<typename T>
|
||||
auto blockOn(kj::Promise<T> && promise);
|
||||
auto blockOn(
|
||||
kj::Promise<T> && promise, std::source_location call_site = std::source_location::current()
|
||||
);
|
||||
};
|
||||
|
||||
inline AsyncContext & AIO()
|
||||
@@ -137,7 +139,14 @@ static constexpr std::optional<std::string> lixAsyncTaskContext()
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
inline auto nix::AsyncIoRoot::blockOn(kj::Promise<T> && promise)
|
||||
{
|
||||
inline auto nix::AsyncIoRoot::blockOn(kj::Promise<T> && 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;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/core/demangle.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
void BaseError::addTrace(std::shared_ptr<Pos> && 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<const ForeignException *>(&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<const BaseException *>(&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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user