since we now propagate goal exceptions properly we no longer need to check topGoals for a reason to abort early. any early abort reasons, whether by exception or a clean top goal failure, can now be handled by inspecting the goal result in the main loop. this greatly reduces goal-to-goal interactions that do not happen at the main loop level. since the underscore-free name is now available for use as variables we'll migrate to that where we currently use `_topGoals` for locals. Change-Id: I5727c5ea7799647c0a69ab76975b1a03a6558aa6
303 lines
9.1 KiB
C++
303 lines
9.1 KiB
C++
#pragma once
|
|
///@file
|
|
|
|
#include "async-semaphore.hh"
|
|
#include "concepts.hh"
|
|
#include "notifying-counter.hh"
|
|
#include "types.hh"
|
|
#include "lock.hh"
|
|
#include "store-api.hh"
|
|
#include "goal.hh"
|
|
#include "realisation.hh"
|
|
|
|
#include <future>
|
|
#include <kj/async-io.h>
|
|
#include <thread>
|
|
|
|
namespace nix {
|
|
|
|
/* Forward definition. */
|
|
struct DerivationGoal;
|
|
struct PathSubstitutionGoal;
|
|
class DrvOutputSubstitutionGoal;
|
|
class LocalStore;
|
|
|
|
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
|
|
|
|
/* Forward definition. */
|
|
struct HookInstance;
|
|
|
|
class GoalFactory
|
|
{
|
|
public:
|
|
virtual std::pair<std::shared_ptr<DerivationGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
|
makeDerivationGoal(
|
|
const StorePath & drvPath, const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal
|
|
) = 0;
|
|
virtual std::pair<std::shared_ptr<DerivationGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
|
makeBasicDerivationGoal(
|
|
const StorePath & drvPath,
|
|
const BasicDerivation & drv,
|
|
const OutputsSpec & wantedOutputs,
|
|
BuildMode buildMode = bmNormal
|
|
) = 0;
|
|
|
|
/**
|
|
* @ref SubstitutionGoal "substitution goal"
|
|
*/
|
|
virtual std::pair<std::shared_ptr<PathSubstitutionGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
|
makePathSubstitutionGoal(
|
|
const StorePath & storePath,
|
|
RepairFlag repair = NoRepair,
|
|
std::optional<ContentAddress> ca = std::nullopt
|
|
) = 0;
|
|
virtual std::pair<std::shared_ptr<DrvOutputSubstitutionGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
|
makeDrvOutputSubstitutionGoal(
|
|
const DrvOutput & id,
|
|
RepairFlag repair = NoRepair,
|
|
std::optional<ContentAddress> ca = std::nullopt
|
|
) = 0;
|
|
|
|
/**
|
|
* Make a goal corresponding to the `DerivedPath`.
|
|
*
|
|
* It will be a `DerivationGoal` for a `DerivedPath::Built` or
|
|
* a `SubstitutionGoal` for a `DerivedPath::Opaque`.
|
|
*/
|
|
virtual std::pair<GoalPtr, kj::Promise<Result<Goal::WorkResult>>>
|
|
makeGoal(const DerivedPath & req, BuildMode buildMode = bmNormal) = 0;
|
|
};
|
|
|
|
// elaborate hoax to let goals access factory methods while hiding them from the public
|
|
class WorkerBase : protected GoalFactory
|
|
{
|
|
friend struct DerivationGoal;
|
|
friend struct PathSubstitutionGoal;
|
|
friend class DrvOutputSubstitutionGoal;
|
|
|
|
protected:
|
|
GoalFactory & goalFactory() { return *this; }
|
|
};
|
|
|
|
/**
|
|
* The worker class.
|
|
*/
|
|
class Worker : public WorkerBase
|
|
{
|
|
public:
|
|
using Targets = std::map<GoalPtr, kj::Promise<Result<Goal::WorkResult>>>;
|
|
|
|
private:
|
|
|
|
bool running = false;
|
|
|
|
template<typename G>
|
|
struct CachedGoal
|
|
{
|
|
std::weak_ptr<G> goal;
|
|
kj::Own<kj::ForkedPromise<Result<Goal::WorkResult>>> promise;
|
|
kj::Own<kj::PromiseFulfiller<Result<Goal::WorkResult>>> fulfiller;
|
|
|
|
CachedGoal()
|
|
{
|
|
auto pf = kj::newPromiseAndFulfiller<Result<Goal::WorkResult>>();
|
|
promise = kj::heap(pf.promise.fork());
|
|
fulfiller = std::move(pf.fulfiller);
|
|
}
|
|
};
|
|
/**
|
|
* Maps used to prevent multiple instantiations of a goal for the
|
|
* same derivation / path.
|
|
*/
|
|
std::map<StorePath, CachedGoal<DerivationGoal>> derivationGoals;
|
|
std::map<StorePath, CachedGoal<PathSubstitutionGoal>> substitutionGoals;
|
|
std::map<DrvOutput, CachedGoal<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
|
|
|
|
/**
|
|
* Cache for pathContentsGood().
|
|
*/
|
|
std::map<StorePath, bool> pathContentsGoodCache;
|
|
|
|
/**
|
|
* Set if at least one derivation had a BuildError (i.e. permanent
|
|
* failure).
|
|
*/
|
|
bool permanentFailure = false;
|
|
|
|
/**
|
|
* Set if at least one derivation had a timeout.
|
|
*/
|
|
bool timedOut = false;
|
|
|
|
/**
|
|
* Set if at least one derivation fails with a hash mismatch.
|
|
*/
|
|
bool hashMismatch = false;
|
|
|
|
/**
|
|
* Set if at least one derivation is not deterministic in check mode.
|
|
*/
|
|
bool checkMismatch = false;
|
|
|
|
void goalFinished(GoalPtr goal, Goal::WorkResult & f);
|
|
|
|
/**
|
|
* Remove a dead goal.
|
|
*/
|
|
void removeGoal(GoalPtr goal);
|
|
|
|
/**
|
|
* Registers a running child process.
|
|
*/
|
|
void childStarted(GoalPtr goal, kj::Promise<Result<Goal::WorkResult>> promise);
|
|
|
|
/**
|
|
* Pass current stats counters to the logger for progress bar updates.
|
|
*/
|
|
kj::Promise<Result<void>> updateStatistics();
|
|
|
|
AsyncSemaphore statisticsUpdateSignal{1};
|
|
std::optional<AsyncSemaphore::Token> statisticsUpdateInhibitor;
|
|
|
|
/**
|
|
* Mark statistics as outdated, such that `updateStatistics` will be called.
|
|
*/
|
|
void updateStatisticsLater()
|
|
{
|
|
statisticsUpdateInhibitor = {};
|
|
}
|
|
|
|
kj::Promise<Result<void>> runImpl(Targets topGoals);
|
|
kj::Promise<Result<void>> boopGC(LocalStore & localStore);
|
|
|
|
public:
|
|
|
|
const Activity act;
|
|
const Activity actDerivations;
|
|
const Activity actSubstitutions;
|
|
|
|
Store & store;
|
|
Store & evalStore;
|
|
kj::AsyncIoContext & aio;
|
|
AsyncSemaphore substitutions, localBuilds;
|
|
|
|
private:
|
|
kj::TaskSet children;
|
|
|
|
public:
|
|
struct HookState {
|
|
std::unique_ptr<HookInstance> instance;
|
|
|
|
/**
|
|
* Whether to ask the build hook if it can build a derivation. If
|
|
* it answers with "decline-permanently", we don't try again.
|
|
*/
|
|
bool available = true;
|
|
};
|
|
|
|
HookState hook;
|
|
|
|
NotifyingCounter<uint64_t> expectedBuilds{[this] { updateStatisticsLater(); }};
|
|
NotifyingCounter<uint64_t> doneBuilds{[this] { updateStatisticsLater(); }};
|
|
NotifyingCounter<uint64_t> failedBuilds{[this] { updateStatisticsLater(); }};
|
|
NotifyingCounter<uint64_t> runningBuilds{[this] { updateStatisticsLater(); }};
|
|
|
|
NotifyingCounter<uint64_t> expectedSubstitutions{[this] { updateStatisticsLater(); }};
|
|
NotifyingCounter<uint64_t> doneSubstitutions{[this] { updateStatisticsLater(); }};
|
|
NotifyingCounter<uint64_t> failedSubstitutions{[this] { updateStatisticsLater(); }};
|
|
NotifyingCounter<uint64_t> runningSubstitutions{[this] { updateStatisticsLater(); }};
|
|
NotifyingCounter<uint64_t> expectedDownloadSize{[this] { updateStatisticsLater(); }};
|
|
NotifyingCounter<uint64_t> doneDownloadSize{[this] { updateStatisticsLater(); }};
|
|
NotifyingCounter<uint64_t> expectedNarSize{[this] { updateStatisticsLater(); }};
|
|
NotifyingCounter<uint64_t> doneNarSize{[this] { updateStatisticsLater(); }};
|
|
|
|
Worker(Store & store, Store & evalStore, kj::AsyncIoContext & aio);
|
|
~Worker();
|
|
|
|
/**
|
|
* Make a goal (with caching).
|
|
*/
|
|
|
|
/**
|
|
* @ref DerivationGoal "derivation goal"
|
|
*/
|
|
private:
|
|
template<typename ID, std::derived_from<Goal> G>
|
|
std::pair<std::shared_ptr<G>, kj::Promise<Result<Goal::WorkResult>>> makeGoalCommon(
|
|
std::map<ID, CachedGoal<G>> & map,
|
|
const ID & key,
|
|
InvocableR<std::unique_ptr<G>> auto create,
|
|
InvocableR<bool, G &> auto modify
|
|
);
|
|
std::pair<std::shared_ptr<DerivationGoal>, kj::Promise<Result<Goal::WorkResult>>> makeDerivationGoal(
|
|
const StorePath & drvPath,
|
|
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal) override;
|
|
std::pair<std::shared_ptr<DerivationGoal>, kj::Promise<Result<Goal::WorkResult>>> makeBasicDerivationGoal(
|
|
const StorePath & drvPath, const BasicDerivation & drv,
|
|
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal) override;
|
|
|
|
/**
|
|
* @ref SubstitutionGoal "substitution goal"
|
|
*/
|
|
std::pair<std::shared_ptr<PathSubstitutionGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
|
makePathSubstitutionGoal(
|
|
const StorePath & storePath,
|
|
RepairFlag repair = NoRepair,
|
|
std::optional<ContentAddress> ca = std::nullopt
|
|
) override;
|
|
std::pair<std::shared_ptr<DrvOutputSubstitutionGoal>, kj::Promise<Result<Goal::WorkResult>>>
|
|
makeDrvOutputSubstitutionGoal(
|
|
const DrvOutput & id,
|
|
RepairFlag repair = NoRepair,
|
|
std::optional<ContentAddress> ca = std::nullopt
|
|
) override;
|
|
|
|
/**
|
|
* Make a goal corresponding to the `DerivedPath`.
|
|
*
|
|
* It will be a `DerivationGoal` for a `DerivedPath::Built` or
|
|
* a `SubstitutionGoal` for a `DerivedPath::Opaque`.
|
|
*/
|
|
std::pair<GoalPtr, kj::Promise<Result<Goal::WorkResult>>>
|
|
makeGoal(const DerivedPath & req, BuildMode buildMode = bmNormal) override;
|
|
|
|
public:
|
|
/**
|
|
* Loop until the specified top-level goals have finished.
|
|
*/
|
|
std::vector<GoalPtr> run(std::function<Targets (GoalFactory &)> req);
|
|
|
|
/***
|
|
* The exit status in case of failure.
|
|
*
|
|
* In the case of a build failure, returned value follows this
|
|
* bitmask:
|
|
*
|
|
* ```
|
|
* 0b1100100
|
|
* ^^^^
|
|
* |||`- timeout
|
|
* ||`-- output hash mismatch
|
|
* |`--- build failure
|
|
* `---- not deterministic
|
|
* ```
|
|
*
|
|
* In other words, the failure code is at least 100 (0b1100100), but
|
|
* might also be greater.
|
|
*
|
|
* Otherwise (no build failure, but some other sort of failure by
|
|
* assumption), this returned value is 1.
|
|
*/
|
|
unsigned int failingExitStatus();
|
|
|
|
/**
|
|
* Check whether the given valid path exists and has the right
|
|
* contents.
|
|
*/
|
|
bool pathContentsGood(const StorePath & path);
|
|
|
|
void markContentsGood(const StorePath & path);
|
|
};
|
|
|
|
}
|