this gives us a semblance of a stack trace if async tasks fail, like:
Async task trace (probably incomplete):
#0: virtual kj::Promise<Result<Goal::WorkResult>> nix::LocalDerivationGoal::tryLocalBuild() (lix/libstore/build/local-derivation-goal.cc:257:33)
building of '//0ppb5aj4p3vp0icnkq4yibajw1czvdja-bash52-009.drv^out' from .drv file
#1: kj::Promise<Result<Goal::WorkResult>> nix::DerivationGoal::outputsSubstitutionTried() (lix/libstore/build/derivation-goal.cc:381:47)
building of '//0ppb5aj4p3vp0icnkq4yibajw1czvdja-bash52-009.drv^out' from .drv file
#2: kj::Promise<Result<Goal::WorkResult>> nix::DerivationGoal::loadDerivation() (lix/libstore/build/derivation-goal.cc:213:41)
building of '//0ppb5aj4p3vp0icnkq4yibajw1czvdja-bash52-009.drv^out' from .drv file
#3: kj::Promise<Result<Goal::WorkResult>> nix::DerivationGoal::gaveUpOnSubstitution() (lix/libstore/build/derivation-goal.cc:453:62)
building of '//f8rkb1vam9y9vi9scsm1v213b01rm72h-bash-5.2p37.drv^out' from .drv file
#4: kj::Promise<Result<Goal::WorkResult>> nix::DerivationGoal::outputsSubstitutionTried() (lix/libstore/build/derivation-goal.cc:381:47)
building of '//f8rkb1vam9y9vi9scsm1v213b01rm72h-bash-5.2p37.drv^out' from .drv file
#5: kj::Promise<Result<Goal::WorkResult>> nix::DerivationGoal::loadDerivation() (lix/libstore/build/derivation-goal.cc:213:41)
building of '//f8rkb1vam9y9vi9scsm1v213b01rm72h-bash-5.2p37.drv^out' from .drv file
#6: kj::Promise<Result<Goal::WorkResult>> nix::DerivationGoal::gaveUpOnSubstitution() (lix/libstore/build/derivation-goal.cc:453:62)
building of '//qa9jvc4j6fphzwr63k5c829b2hr04q6z-foo.drv^*' from .drv file
#7: kj::Promise<Result<Goal::WorkResult>> nix::DerivationGoal::outputsSubstitutionTried() (lix/libstore/build/derivation-goal.cc:381:47)
building of '//qa9jvc4j6fphzwr63k5c829b2hr04q6z-foo.drv^*' from .drv file
#8: kj::Promise<Result<Goal::WorkResult>> nix::DerivationGoal::loadDerivation() (lix/libstore/build/derivation-goal.cc:213:41)
building of '//qa9jvc4j6fphzwr63k5c829b2hr04q6z-foo.drv^*' from .drv file
#9: virtual kj::Promise<Result<void>> nix::Store::buildPaths(const std::vector<DerivedPath> &, BuildMode, std::shared_ptr<Store>) (lix/libstore/build/entry-points.cc:20:11)
it's not much, but it's a lot better than nothing. sadly we're forced
to disable another clang-tidy warning now, but since that one's about
assert side effects being compiled out in release mode and we've long
since decided to just not do that we are not affected by this at all.
also see #724 for a related but different approach to origin tracing.
Change-Id: I8161f82ca39d0b271316ea4df80513acc1b06a03
144 lines
4.3 KiB
C++
144 lines
4.3 KiB
C++
#pragma once
|
|
///@file
|
|
|
|
#include "lix/libutil/error.hh"
|
|
#include "lix/libutil/result.hh"
|
|
#include "lix/libutil/signals.hh"
|
|
#include <future>
|
|
#include <kj/async-io.h>
|
|
#include <kj/async.h>
|
|
#include <optional>
|
|
#include <source_location>
|
|
|
|
namespace nix {
|
|
|
|
struct AsyncContext
|
|
{
|
|
static inline thread_local AsyncContext * current = nullptr;
|
|
|
|
kj::AsyncIoProvider & provider;
|
|
kj::UnixEventPort & unixEventPort;
|
|
|
|
explicit AsyncContext(kj::AsyncIoContext & aio)
|
|
: provider(*aio.provider)
|
|
, unixEventPort(aio.unixEventPort)
|
|
{
|
|
assert(current == nullptr);
|
|
current = this;
|
|
}
|
|
|
|
~AsyncContext()
|
|
{
|
|
current = nullptr;
|
|
}
|
|
|
|
KJ_DISALLOW_COPY_AND_MOVE(AsyncContext);
|
|
};
|
|
|
|
struct AsyncIoRoot
|
|
{
|
|
kj::AsyncIoContext kj;
|
|
AsyncContext context;
|
|
|
|
AsyncIoRoot() : kj(kj::setupAsyncIo()), context(kj) {}
|
|
KJ_DISALLOW_COPY_AND_MOVE(AsyncIoRoot);
|
|
|
|
template<typename T>
|
|
auto blockOn(kj::Promise<T> && promise);
|
|
};
|
|
|
|
inline AsyncContext & AIO()
|
|
{
|
|
assert(AsyncContext::current != nullptr);
|
|
return *AsyncContext::current;
|
|
}
|
|
|
|
namespace detail {
|
|
inline void materializeResult(Result<void> r)
|
|
{
|
|
r.value();
|
|
}
|
|
|
|
template<typename T>
|
|
inline T materializeResult(Result<T> r)
|
|
{
|
|
return std::move(r.value());
|
|
}
|
|
|
|
template<typename T>
|
|
T runAsyncUnwrap(T t)
|
|
{
|
|
return t;
|
|
}
|
|
template<typename T>
|
|
T runAsyncUnwrap(Result<T> t)
|
|
{
|
|
return std::move(t).value();
|
|
}
|
|
|
|
auto runAsyncInNewThread(std::invocable<AsyncIoRoot &> auto fn)
|
|
{
|
|
auto future = std::async(std::launch::async, [&] {
|
|
ReceiveInterrupts ri;
|
|
AsyncIoRoot aioRoot;
|
|
if constexpr (!std::is_void_v<decltype(fn(aioRoot))>) {
|
|
return runAsyncUnwrap(fn(aioRoot));
|
|
} else {
|
|
fn(aioRoot);
|
|
}
|
|
});
|
|
return future.get();
|
|
}
|
|
}
|
|
}
|
|
|
|
#define LIX_RUN_ASYNC_IN_NEW_THREAD(...) \
|
|
::nix::detail::runAsyncInNewThread([&](AsyncIoRoot & AIOROOT) { \
|
|
return AIOROOT.blockOn(__VA_ARGS__); \
|
|
})
|
|
|
|
#define LIX_TRY_AWAIT_CONTEXT(ctx, ...) \
|
|
({ \
|
|
auto _lix_awaited = co_await (__VA_ARGS__); \
|
|
if (_lix_awaited.has_error()) { \
|
|
try { \
|
|
_lix_awaited.value(); \
|
|
} catch (::nix::BaseException & e) { \
|
|
e.addAsyncTrace(::std::source_location::current(), ctx()); \
|
|
throw; \
|
|
} catch (::std::exception & e) { /* NOLINT(lix-foreign-exceptions) */ \
|
|
::nix::ForeignException fe(e); \
|
|
fe.addAsyncTrace(::std::source_location::current(), ctx()); \
|
|
throw fe; \
|
|
} \
|
|
} \
|
|
::nix::detail::materializeResult(std::move(_lix_awaited)); \
|
|
})
|
|
|
|
/**
|
|
* Magic name used by `LIX_TRY_AWAIT` to insert additional context into an
|
|
* async trace frame. This name will be looked up in the local scope every
|
|
* time a try-await expression encounters an exception and then called. As
|
|
* such it can be a function, a member function name, or even a type name.
|
|
*/
|
|
static constexpr std::optional<std::string> lixAsyncTaskContext()
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
// force materialization of the value. result::value() returns only an rvalue reference
|
|
// and is thus unsuitable for use in e.g. range for without materialization. ideally we
|
|
// would wrap the expression in `auto()`, but apple clang fails when given `auto(void)`
|
|
#define LIX_TRY_AWAIT(...) LIX_TRY_AWAIT_CONTEXT(lixAsyncTaskContext, __VA_ARGS__)
|
|
|
|
#if LIX_UR_COMPILER_UWU
|
|
#define RUN_ASYNC_IN_NEW_THREAD LIX_RUN_ASYNC_IN_NEW_THREAD
|
|
#define TRY_AWAIT LIX_TRY_AWAIT
|
|
#endif
|
|
|
|
template<typename T>
|
|
inline auto nix::AsyncIoRoot::blockOn(kj::Promise<T> && promise)
|
|
{
|
|
return detail::runAsyncUnwrap(promise.wait(kj.waitScope));
|
|
}
|