Skip to content

Commit f713508

Browse files
committed
Revert "Revert "Adapt scheduler to work with dynamic derivations""
This fixes dynamic derivations, reverting #9081. I believe that this time around, #9052 is fixed. When I first rebased this, tests were failing (which wasn't the case before). The cause of those test failures were due to the crude job in which the outer goal tried to exit with the inner goal's status. Now, that error handling has been reworked to be more faithful. The exit exit status and exception of the inner goal is returned by the outer goal. The exception was what was causing the test failures, but I believe it was not having the right error code (there is more than one for failure) that caused #9081. The only cost of doing things the "right way" was that I had to introduce a hacky `preserveException` boolean. I don't like this, but, then again, none of us like anything about how the scheduler works. Issue #11927 is still there to clean everything up, subsuming the need for any `preserveException` because I doubt we will be fishing information out of state machines like this at all. This reverts commit 8440afb.
1 parent 4f96f24 commit f713508

14 files changed

+371
-42
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#include "create-derivation-and-realise-goal.hh"
2+
#include "worker.hh"
3+
4+
namespace nix {
5+
6+
CreateDerivationAndRealiseGoal::CreateDerivationAndRealiseGoal(
7+
ref<SingleDerivedPath> drvReq, const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
8+
: Goal(worker, DerivedPath::Built{.drvPath = drvReq, .outputs = wantedOutputs})
9+
, drvReq(drvReq)
10+
, wantedOutputs(wantedOutputs)
11+
, buildMode(buildMode)
12+
{
13+
name =
14+
fmt("outer obtaining drv from '%s' and then building outputs %s",
15+
drvReq->to_string(worker.store),
16+
std::visit(
17+
overloaded{
18+
[&](const OutputsSpec::All) -> std::string { return "* (all of them)"; },
19+
[&](const OutputsSpec::Names os) { return concatStringsSep(", ", quoteStrings(os)); },
20+
},
21+
wantedOutputs.raw));
22+
trace("created outer");
23+
24+
worker.updateProgress();
25+
}
26+
27+
CreateDerivationAndRealiseGoal::~CreateDerivationAndRealiseGoal() {}
28+
29+
static StorePath pathPartOfReq(const SingleDerivedPath & req)
30+
{
31+
return std::visit(
32+
overloaded{
33+
[&](const SingleDerivedPath::Opaque & bo) { return bo.path; },
34+
[&](const SingleDerivedPath::Built & bfd) { return pathPartOfReq(*bfd.drvPath); },
35+
},
36+
req.raw());
37+
}
38+
39+
std::string CreateDerivationAndRealiseGoal::key()
40+
{
41+
/* Ensure that derivations get built in order of their name,
42+
i.e. a derivation named "aardvark" always comes before "baboon". And
43+
substitution goals and inner derivation goals always happen before
44+
derivation goals (due to "b$"). */
45+
return "c$" + std::string(pathPartOfReq(*drvReq).name()) + "$" + drvReq->to_string(worker.store);
46+
}
47+
48+
void CreateDerivationAndRealiseGoal::timedOut(Error && ex) {}
49+
50+
void CreateDerivationAndRealiseGoal::addWantedOutputs(const OutputsSpec & outputs)
51+
{
52+
/* If we already want all outputs, there is nothing to do. */
53+
auto newWanted = wantedOutputs.union_(outputs);
54+
bool needRestart = !newWanted.isSubsetOf(wantedOutputs);
55+
wantedOutputs = newWanted;
56+
57+
if (!needRestart)
58+
return;
59+
60+
if (!optDrvPath)
61+
// haven't started steps where the outputs matter yet
62+
return;
63+
worker.makeDerivationGoal(*optDrvPath, outputs, buildMode);
64+
}
65+
66+
Goal::Co CreateDerivationAndRealiseGoal::init()
67+
{
68+
trace("outer init");
69+
70+
/* The first thing to do is to make sure that the derivation
71+
exists. If it doesn't, it may be created through a
72+
substitute. */
73+
if (auto optDrvPath = [this]() -> std::optional<StorePath> {
74+
if (buildMode != bmNormal)
75+
return std::nullopt;
76+
77+
auto drvPath = StorePath::dummy;
78+
try {
79+
drvPath = resolveDerivedPath(worker.store, *drvReq);
80+
} catch (MissingRealisation &) {
81+
return std::nullopt;
82+
}
83+
auto cond = worker.evalStore.isValidPath(drvPath) || worker.store.isValidPath(drvPath);
84+
return cond ? std::optional{drvPath} : std::nullopt;
85+
}()) {
86+
trace(
87+
fmt("already have drv '%s' for '%s', can go straight to building",
88+
worker.store.printStorePath(*optDrvPath),
89+
drvReq->to_string(worker.store)));
90+
} else {
91+
trace("need to obtain drv we want to build");
92+
addWaitee(worker.makeGoal(DerivedPath::fromSingle(*drvReq)));
93+
co_await Suspend{};
94+
}
95+
96+
trace("outer load and build derivation");
97+
98+
if (nrFailed != 0) {
99+
co_return amDone(ecFailed, Error("cannot build missing derivation '%s'", drvReq->to_string(worker.store)));
100+
}
101+
102+
StorePath drvPath = resolveDerivedPath(worker.store, *drvReq);
103+
/* Build this step! */
104+
concreteDrvGoal = worker.makeDerivationGoal(drvPath, wantedOutputs, buildMode);
105+
{
106+
auto g = upcast_goal(concreteDrvGoal);
107+
/* We will finish with it ourselves, as if we were the derivational goal. */
108+
g->preserveException = true;
109+
}
110+
optDrvPath = std::move(drvPath);
111+
addWaitee(upcast_goal(concreteDrvGoal));
112+
co_await Suspend{};
113+
114+
trace("outer build done");
115+
116+
buildResult = upcast_goal(concreteDrvGoal)
117+
->getBuildResult(DerivedPath::Built{
118+
.drvPath = drvReq,
119+
.outputs = wantedOutputs,
120+
});
121+
122+
auto g = upcast_goal(concreteDrvGoal);
123+
co_return amDone(g->exitCode, g->ex);
124+
}
125+
126+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#pragma once
2+
3+
#include "parsed-derivations.hh"
4+
#include "user-lock.hh"
5+
#include "store-api.hh"
6+
#include "pathlocks.hh"
7+
#include "goal.hh"
8+
9+
namespace nix {
10+
11+
struct DerivationGoal;
12+
13+
/**
14+
* This goal type is essentially the serial composition (like function
15+
* composition) of a goal for getting a derivation, and then a
16+
* `DerivationGoal` using the newly-obtained derivation.
17+
*
18+
* In the (currently experimental) general inductive case of derivations
19+
* that are themselves build outputs, that first goal will be *another*
20+
* `CreateDerivationAndRealiseGoal`. In the (much more common) base-case
21+
* where the derivation has no provence and is just referred to by
22+
* (content-addressed) store path, that first goal is a
23+
* `SubstitutionGoal`.
24+
*
25+
* If we already have the derivation (e.g. if the evalutator has created
26+
* the derivation locally and then instructured the store to build it),
27+
* we can skip the first goal entirely as a small optimization.
28+
*/
29+
struct CreateDerivationAndRealiseGoal : public Goal
30+
{
31+
/**
32+
* How to obtain a store path of the derivation to build.
33+
*/
34+
ref<SingleDerivedPath> drvReq;
35+
36+
/**
37+
* The path of the derivation, once obtained.
38+
**/
39+
std::optional<StorePath> optDrvPath;
40+
41+
/**
42+
* The goal for the corresponding concrete derivation.
43+
**/
44+
std::shared_ptr<DerivationGoal> concreteDrvGoal;
45+
46+
/**
47+
* The specific outputs that we need to build.
48+
*/
49+
OutputsSpec wantedOutputs;
50+
51+
/**
52+
* The final output paths of the build.
53+
*
54+
* - For input-addressed derivations, always the precomputed paths
55+
*
56+
* - For content-addressed derivations, calcuated from whatever the
57+
* hash ends up being. (Note that fixed outputs derivations that
58+
* produce the "wrong" output still install that data under its
59+
* true content-address.)
60+
*/
61+
OutputPathMap finalOutputs;
62+
63+
BuildMode buildMode;
64+
65+
CreateDerivationAndRealiseGoal(
66+
ref<SingleDerivedPath> drvReq,
67+
const OutputsSpec & wantedOutputs,
68+
Worker & worker,
69+
BuildMode buildMode = bmNormal);
70+
virtual ~CreateDerivationAndRealiseGoal();
71+
72+
void timedOut(Error && ex) override;
73+
74+
std::string key() override;
75+
76+
/**
77+
* Add wanted outputs to an already existing derivation goal.
78+
*/
79+
void addWantedOutputs(const OutputsSpec & outputs);
80+
81+
Co init() override;
82+
83+
JobCategory jobCategory() const override
84+
{
85+
return JobCategory::Administration;
86+
};
87+
};
88+
89+
}

src/libstore/build/derivation-goal.cc

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -137,21 +137,8 @@ Goal::Co DerivationGoal::init() {
137137
trace("init");
138138

139139
if (useDerivation) {
140-
/* The first thing to do is to make sure that the derivation
141-
exists. If it doesn't, it may be created through a
142-
substitute. */
143-
144-
if (buildMode != bmNormal || !worker.evalStore.isValidPath(drvPath)) {
145-
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(drvPath)));
146-
co_await Suspend{};
147-
}
148-
149140
trace("loading derivation");
150141

151-
if (nrFailed != 0) {
152-
co_return done(BuildResult::MiscFailure, {}, Error("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)));
153-
}
154-
155142
/* `drvPath' should already be a root, but let's be on the safe
156143
side: if the user forgot to make it a root, we wouldn't want
157144
things being garbage collected while we're busy. */
@@ -1549,23 +1536,24 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
15491536
if (!useDerivation || !drv) return;
15501537
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
15511538

1552-
auto * dg = dynamic_cast<DerivationGoal *>(&*waitee);
1553-
if (!dg) return;
1539+
std::optional info = tryGetConcreteDrvGoal(waitee);
1540+
if (!info) return;
1541+
const auto & [dg, drvReq] = *info;
15541542

1555-
auto * nodeP = fullDrv.inputDrvs.findSlot(DerivedPath::Opaque { .path = dg->drvPath });
1543+
auto * nodeP = fullDrv.inputDrvs.findSlot(drvReq.get());
15561544
if (!nodeP) return;
15571545
auto & outputs = nodeP->value;
15581546

15591547
for (auto & outputName : outputs) {
1560-
auto buildResult = dg->getBuildResult(DerivedPath::Built {
1561-
.drvPath = makeConstantStorePathRef(dg->drvPath),
1548+
auto buildResult = dg.get().getBuildResult(DerivedPath::Built {
1549+
.drvPath = makeConstantStorePathRef(dg.get().drvPath),
15621550
.outputs = OutputsSpec::Names { outputName },
15631551
});
15641552
if (buildResult.success()) {
15651553
auto i = buildResult.builtOutputs.find(outputName);
15661554
if (i != buildResult.builtOutputs.end())
15671555
inputDrvOutputs.insert_or_assign(
1568-
{ dg->drvPath, outputName },
1556+
{ dg.get().drvPath, outputName },
15691557
i->second.outPath);
15701558
}
15711559
}

src/libstore/build/derivation-goal.hh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ struct InitialOutput {
5656

5757
/**
5858
* A goal for building some or all of the outputs of a derivation.
59+
*
60+
* The derivation must already be present, either in the store in a drv
61+
* or in memory. If the derivation itself needs to be gotten first, a
62+
* `CreateDerivationAndRealiseGoal` goal must be used instead.
5963
*/
6064
struct DerivationGoal : public Goal
6165
{

src/libstore/build/entry-points.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "worker.hh"
22
#include "substitution-goal.hh"
33
#ifndef _WIN32 // TODO Enable building on Windows
4+
# include "create-derivation-and-realise-goal.hh"
45
# include "derivation-goal.hh"
56
#endif
67
#include "local-store.hh"
@@ -29,8 +30,8 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
2930
}
3031
if (i->exitCode != Goal::ecSuccess) {
3132
#ifndef _WIN32 // TODO Enable building on Windows
32-
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
33-
failed.insert(printStorePath(i2->drvPath));
33+
if (auto i2 = dynamic_cast<CreateDerivationAndRealiseGoal *>(i.get()))
34+
failed.insert(i2->drvReq->to_string(*this));
3435
else
3536
#endif
3637
if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))

src/libstore/build/goal.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ Goal::Done Goal::amDone(ExitCode result, std::optional<Error> ex)
175175
exitCode = result;
176176

177177
if (ex) {
178-
if (!waiters.empty())
178+
if (!preserveException && !waiters.empty())
179179
logError(ex->info());
180180
else
181181
this->ex = std::move(*ex);

src/libstore/build/goal.hh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ enum struct JobCategory {
5050
* A substitution an arbitrary store object; it will use network resources.
5151
*/
5252
Substitution,
53+
/**
54+
* A goal that does no "real" work by itself, and just exists to depend on
55+
* other goals which *do* do real work. These goals therefore are not
56+
* limited.
57+
*
58+
* These goals cannot infinitely create themselves, so there is no risk of
59+
* a "fork bomb" type situation (which would be a problem even though the
60+
* goal do no real work) either.
61+
*/
62+
Administration,
5363
};
5464

5565
struct Goal : public std::enable_shared_from_this<Goal>
@@ -373,6 +383,17 @@ public:
373383
*/
374384
BuildResult getBuildResult(const DerivedPath &) const;
375385

386+
/**
387+
* Hack to say that this goal should not log `ex`, but instead keep
388+
* it around. Set by a waitee which sees itself as the designated
389+
* continuation of this goal, responsible for reporting its
390+
* successes or failures.
391+
*
392+
* @todo this is yet another not-nice hack in the goal system that
393+
* we ought to get rid of. See #11927
394+
*/
395+
bool preserveException = false;
396+
376397
/**
377398
* Exception containing an error message, if any.
378399
*/

0 commit comments

Comments
 (0)