mirror of
https://github.com/NixOS/nix.git
synced 2025-11-22 10:19:36 +01:00
* It is tough to contribute to a project that doesn't use a formatter, * It is extra hard to contribute to a project which has configured the formatter, but ignores it for some files * Code formatting makes it harder to hide obscure / weird bugs by accident or on purpose, Let's rip the bandaid off? Note that PRs currently in flight should be able to be merged relatively easily by applying `clang-format` to their tip prior to merge. Co-authored-by: Graham Christensen <graham@grahamc.com>
255 lines
7.3 KiB
C++
255 lines
7.3 KiB
C++
#include "nix/store/build/goal.hh"
|
|
#include "nix/store/build/worker.hh"
|
|
|
|
namespace nix {
|
|
|
|
using Co = nix::Goal::Co;
|
|
using promise_type = nix::Goal::promise_type;
|
|
using handle_type = nix::Goal::handle_type;
|
|
using Suspend = nix::Goal::Suspend;
|
|
|
|
Co::Co(Co && rhs)
|
|
{
|
|
this->handle = rhs.handle;
|
|
rhs.handle = nullptr;
|
|
}
|
|
|
|
void Co::operator=(Co && rhs)
|
|
{
|
|
this->handle = rhs.handle;
|
|
rhs.handle = nullptr;
|
|
}
|
|
|
|
Co::~Co()
|
|
{
|
|
if (handle) {
|
|
handle.promise().alive = false;
|
|
handle.destroy();
|
|
}
|
|
}
|
|
|
|
Co promise_type::get_return_object()
|
|
{
|
|
auto handle = handle_type::from_promise(*this);
|
|
return Co{handle};
|
|
};
|
|
|
|
std::coroutine_handle<> promise_type::final_awaiter::await_suspend(handle_type h) noexcept
|
|
{
|
|
auto & p = h.promise();
|
|
auto goal = p.goal;
|
|
assert(goal);
|
|
goal->trace("in final_awaiter");
|
|
auto c = std::move(p.continuation);
|
|
|
|
if (c) {
|
|
// We still have a continuation, i.e. work to do.
|
|
// We assert that the goal is still busy.
|
|
assert(goal->exitCode == ecBusy);
|
|
assert(goal->top_co); // Goal must have an active coroutine.
|
|
assert(goal->top_co->handle == h); // The active coroutine must be us.
|
|
assert(p.alive); // We must not have been destructed.
|
|
|
|
// we move continuation to the top,
|
|
// note: previous top_co is actually h, so by moving into it,
|
|
// we're calling the destructor on h, DON'T use h and p after this!
|
|
|
|
// We move our continuation into `top_co`, i.e. the marker for the active continuation.
|
|
// By doing this we destruct the old `top_co`, i.e. us, so `h` can't be used anymore.
|
|
// Be careful not to access freed memory!
|
|
goal->top_co = std::move(c);
|
|
|
|
// We resume `top_co`.
|
|
return goal->top_co->handle;
|
|
} else {
|
|
// We have no continuation, i.e. no more work to do,
|
|
// so the goal must not be busy anymore.
|
|
assert(goal->exitCode != ecBusy);
|
|
|
|
// We reset `top_co` for good measure.
|
|
p.goal->top_co = {};
|
|
|
|
// We jump to the noop coroutine, which doesn't do anything and immediately suspends.
|
|
// This passes control back to the caller of goal.work().
|
|
return std::noop_coroutine();
|
|
}
|
|
}
|
|
|
|
void promise_type::return_value(Co && next)
|
|
{
|
|
goal->trace("return_value(Co&&)");
|
|
// Save old continuation.
|
|
auto old_continuation = std::move(continuation);
|
|
// We set next as our continuation.
|
|
continuation = std::move(next);
|
|
// We set next's goal, and thus it must not have one already.
|
|
assert(!continuation->handle.promise().goal);
|
|
continuation->handle.promise().goal = goal;
|
|
// Nor can next have a continuation, as we set it to our old one.
|
|
assert(!continuation->handle.promise().continuation);
|
|
continuation->handle.promise().continuation = std::move(old_continuation);
|
|
}
|
|
|
|
std::coroutine_handle<> nix::Goal::Co::await_suspend(handle_type caller)
|
|
{
|
|
assert(handle); // we must be a valid coroutine
|
|
auto & p = handle.promise();
|
|
assert(!p.continuation); // we must have no continuation
|
|
assert(!p.goal); // we must not have a goal yet
|
|
auto goal = caller.promise().goal;
|
|
assert(goal);
|
|
p.goal = goal;
|
|
p.continuation = std::move(goal->top_co); // we set our continuation to be top_co (i.e. caller)
|
|
goal->top_co = std::move(*this); // we set top_co to ourselves, don't use this anymore after this!
|
|
return p.goal->top_co->handle; // we execute ourselves
|
|
}
|
|
|
|
bool CompareGoalPtrs::operator()(const GoalPtr & a, const GoalPtr & b) const
|
|
{
|
|
std::string s1 = a->key();
|
|
std::string s2 = b->key();
|
|
return s1 < s2;
|
|
}
|
|
|
|
BuildResult Goal::getBuildResult(const DerivedPath & req) const
|
|
{
|
|
BuildResult res{buildResult};
|
|
|
|
if (auto pbp = std::get_if<DerivedPath::Built>(&req)) {
|
|
auto & bp = *pbp;
|
|
|
|
/* Because goals are in general shared between derived paths
|
|
that share the same derivation, we need to filter their
|
|
results to get back just the results we care about.
|
|
*/
|
|
|
|
for (auto it = res.builtOutputs.begin(); it != res.builtOutputs.end();) {
|
|
if (bp.outputs.contains(it->first))
|
|
++it;
|
|
else
|
|
it = res.builtOutputs.erase(it);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void addToWeakGoals(WeakGoals & goals, GoalPtr p)
|
|
{
|
|
if (goals.find(p) != goals.end())
|
|
return;
|
|
goals.insert(p);
|
|
}
|
|
|
|
Co Goal::await(Goals new_waitees)
|
|
{
|
|
assert(waitees.empty());
|
|
if (!new_waitees.empty()) {
|
|
waitees = std::move(new_waitees);
|
|
for (auto waitee : waitees) {
|
|
addToWeakGoals(waitee->waiters, shared_from_this());
|
|
}
|
|
co_await Suspend{};
|
|
assert(waitees.empty());
|
|
}
|
|
co_return Return{};
|
|
}
|
|
|
|
Goal::Done Goal::amDone(ExitCode result, std::optional<Error> ex)
|
|
{
|
|
trace("done");
|
|
assert(top_co);
|
|
assert(exitCode == ecBusy);
|
|
assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure);
|
|
exitCode = result;
|
|
|
|
if (ex) {
|
|
if (!waiters.empty())
|
|
logError(ex->info());
|
|
else
|
|
this->ex = std::move(*ex);
|
|
}
|
|
|
|
for (auto & i : waiters) {
|
|
GoalPtr goal = i.lock();
|
|
if (goal) {
|
|
auto me = shared_from_this();
|
|
assert(goal->waitees.count(me));
|
|
goal->waitees.erase(me);
|
|
|
|
goal->trace(fmt("waitee '%s' done; %d left", name, goal->waitees.size()));
|
|
|
|
if (result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure)
|
|
++goal->nrFailed;
|
|
|
|
if (result == ecNoSubstituters)
|
|
++goal->nrNoSubstituters;
|
|
|
|
if (result == ecIncompleteClosure)
|
|
++goal->nrIncompleteClosure;
|
|
|
|
if (goal->waitees.empty()) {
|
|
worker.wakeUp(goal);
|
|
} else if (result == ecFailed && !settings.keepGoing) {
|
|
/* If we failed and keepGoing is not set, we remove all
|
|
remaining waitees. */
|
|
for (auto & g : goal->waitees) {
|
|
g->waiters.extract(goal);
|
|
}
|
|
goal->waitees.clear();
|
|
|
|
worker.wakeUp(goal);
|
|
}
|
|
}
|
|
}
|
|
waiters.clear();
|
|
worker.removeGoal(shared_from_this());
|
|
|
|
cleanup();
|
|
|
|
// We drop the continuation.
|
|
// In `final_awaiter` this will signal that there is no more work to be done.
|
|
top_co->handle.promise().continuation = {};
|
|
|
|
// won't return to caller because of logic in final_awaiter
|
|
return Done{};
|
|
}
|
|
|
|
void Goal::trace(std::string_view s)
|
|
{
|
|
debug("%1%: %2%", name, s);
|
|
}
|
|
|
|
void Goal::work()
|
|
{
|
|
assert(top_co);
|
|
assert(top_co->handle);
|
|
assert(top_co->handle.promise().alive);
|
|
top_co->handle.resume();
|
|
// We either should be in a state where we can be work()-ed again,
|
|
// or we should be done.
|
|
assert(top_co || exitCode != ecBusy);
|
|
}
|
|
|
|
Goal::Co Goal::yield()
|
|
{
|
|
worker.wakeUp(shared_from_this());
|
|
co_await Suspend{};
|
|
co_return Return{};
|
|
}
|
|
|
|
Goal::Co Goal::waitForAWhile()
|
|
{
|
|
worker.waitForAWhile(shared_from_this());
|
|
co_await Suspend{};
|
|
co_return Return{};
|
|
}
|
|
|
|
Goal::Co Goal::waitForBuildSlot()
|
|
{
|
|
worker.waitForBuildSlot(shared_from_this());
|
|
co_await Suspend{};
|
|
co_return Return{};
|
|
}
|
|
|
|
} // namespace nix
|