Skip to content

Commit 9aace5a

Browse files
committed
Rework goals to fix NixOS#11928
First step, dones *NOT* fix the issue, but does introduce some new goal types. Fixes NixOS#11928
1 parent bc1fb6e commit 9aace5a

14 files changed

+585
-132
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
#include "nix/store/build/build-trace-goal.hh"
2+
#include "nix/store/build/derivation-resolution-goal.hh"
3+
#include "nix/store/build/substitution-goal.hh"
4+
#include "nix/store/build/worker.hh"
5+
#include "nix/store/derivations.hh"
6+
#include "nix/store/store-open.hh"
7+
#include "nix/util/callback.hh"
8+
#include "nix/util/finally.hh"
9+
#include "nix/util/util.hh"
10+
11+
namespace nix {
12+
13+
BuildTraceGoal::BuildTraceGoal(const SingleDerivedPath::Built & id, Worker & worker)
14+
: Goal{worker, init()}
15+
, id{id}
16+
{
17+
name = fmt("substitution of '%s'", id.to_string(worker.store));
18+
trace("created");
19+
}
20+
21+
Goal::Co BuildTraceGoal::init()
22+
{
23+
trace("init");
24+
25+
DrvOutput id2{
26+
.drvHash = Hash::dummy,
27+
.outputName = id.output,
28+
};
29+
30+
StorePath drvPath = StorePath::dummy;
31+
32+
// No `std::visit` with coroutines :(
33+
if (const auto * path = std::get_if<SingleDerivedPath::Opaque>(&*id.drvPath)) {
34+
// At least we know the drv path statically, can procede
35+
drvPath = path->path;
36+
} else if (const auto * outputDeriving = std::get_if<SingleDerivedPath::Built>(&*id.drvPath)) {
37+
// Dynamic derivation case, need to resolve that first.
38+
39+
auto g = worker.makeBuildTraceGoal({
40+
outputDeriving->drvPath,
41+
outputDeriving->output,
42+
});
43+
44+
co_await await(Goals{upcast_goal(g)});
45+
46+
if (nrFailed > 0) {
47+
co_return amDone(
48+
nrNoSubstituters > 0 ? ecNoSubstituters : ecFailed,
49+
Error("The output deriving path '%s' could not be resolved", outputDeriving->to_string(worker.store)));
50+
}
51+
52+
drvPath = g->outputInfo->outPath;
53+
}
54+
55+
{
56+
auto getStaticHash = [&](Store & store, const Derivation & drv) {
57+
// TODO slow
58+
auto hashes = staticOutputHashes(store, drv);
59+
auto * p = get(hashes, id.output);
60+
if (!p)
61+
throw Error("Missing output '%s' for derivation '%s'", id.output, worker.store.printStorePath(drvPath));
62+
id2.drvHash = *p;
63+
};
64+
65+
if (worker.evalStore.isValidPath(drvPath)) {
66+
auto drv = worker.evalStore.readDerivation(drvPath);
67+
getStaticHash(worker.evalStore, drv);
68+
} else if (worker.store.isValidPath(drvPath)) {
69+
auto drv = worker.store.readDerivation(drvPath);
70+
getStaticHash(worker.evalStore, drv);
71+
}
72+
}
73+
74+
/* If the derivation already exists, we’re done */
75+
if ((outputInfo = worker.store.queryRealisation(id2))) {
76+
co_return amDone(ecSuccess);
77+
}
78+
79+
/**
80+
* Firstly, whether we know the status, secondly, what it is
81+
*/
82+
std::optional<bool> drvIsResolved;
83+
84+
auto checkStaticOutputs = [&](Store & store) -> std::optional<Goal::Done> {
85+
auto drv = store.readDerivation(drvPath);
86+
auto os = drv.outputsAndOptPaths(worker.store);
87+
/* Mark what we now know */
88+
drvIsResolved = {drv.inputDrvs.map.empty()};
89+
if (auto * p = get(os, id2.outputName)) {
90+
if (auto & outPath = p->second) {
91+
outputInfo = std::make_shared<Realisation>(id2, *outPath);
92+
return {amDone(ecSuccess)};
93+
} else {
94+
/* Otherwise, not failure, just looking up build trace below. */
95+
return std::nullopt;
96+
}
97+
} else {
98+
return {amDone(
99+
ecFailed,
100+
Error(
101+
"Derivation '%s' does not have output '%s', impossible to find build trace key-value pair",
102+
worker.store.printStorePath(drvPath),
103+
id2.outputName))};
104+
}
105+
};
106+
107+
/* If we have the derivaiton, and the derivation has statically-known output paths */
108+
if (worker.evalStore.isValidPath(drvPath)) {
109+
if (auto co = checkStaticOutputs(worker.evalStore))
110+
co_return *co;
111+
} else if (worker.store.isValidPath(drvPath)) {
112+
if (auto co = checkStaticOutputs(worker.store))
113+
co_return *co;
114+
}
115+
116+
auto subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
117+
118+
bool substituterFailed = false;
119+
120+
if (!drvIsResolved || *drvIsResolved) {
121+
/* Since derivation might be resolved --- isn't known to be
122+
not-resolved, it might have entries. So, let's try querying
123+
the substituters. */
124+
for (const auto & sub : subs) {
125+
trace("trying next substituter");
126+
127+
/* The callback of the curl download below can outlive `this` (if
128+
some other error occurs), so it must not touch `this`. So put
129+
the shared state in a separate refcounted object. */
130+
auto outPipe = std::make_shared<MuxablePipe>();
131+
#ifndef _WIN32
132+
outPipe->create();
133+
#else
134+
outPipe->createAsyncPipe(worker.ioport.get());
135+
#endif
136+
137+
auto promise = std::make_shared<std::promise<std::shared_ptr<const Realisation>>>();
138+
139+
sub->queryRealisation(
140+
id2, {[outPipe(outPipe), promise(promise)](std::future<std::shared_ptr<const Realisation>> res) {
141+
try {
142+
Finally updateStats([&]() { outPipe->writeSide.close(); });
143+
promise->set_value(res.get());
144+
} catch (...) {
145+
promise->set_exception(std::current_exception());
146+
}
147+
}});
148+
149+
worker.childStarted(
150+
shared_from_this(),
151+
{
152+
#ifndef _WIN32
153+
outPipe->readSide.get()
154+
#else
155+
&*outPipe
156+
#endif
157+
},
158+
true,
159+
false);
160+
161+
co_await Suspend{};
162+
163+
worker.childTerminated(this);
164+
165+
std::shared_ptr<const Realisation> outputInfo;
166+
try {
167+
outputInfo = promise->get_future().get();
168+
} catch (std::exception & e) {
169+
printError(e.what());
170+
substituterFailed = true;
171+
}
172+
173+
if (!outputInfo)
174+
continue;
175+
176+
worker.store.registerDrvOutput(*outputInfo);
177+
178+
trace("finished");
179+
co_return amDone(ecSuccess);
180+
}
181+
}
182+
183+
/* Derivation might not be resolved, let's try doing that */
184+
trace("trying resolving derivation in build-trace goal");
185+
186+
auto g = worker.makeDerivationResolutionGoal(drvPath);
187+
g->preserveException = true;
188+
co_await await(Goals{g});
189+
190+
if (nrFailed > 0) {
191+
/* None left. Terminate this goal and let someone else deal
192+
with it. */
193+
194+
if (substituterFailed) {
195+
worker.failedSubstitutions++;
196+
worker.updateProgress();
197+
}
198+
199+
/* Hack: don't indicate failure if there were no substituters.
200+
In that case the calling derivation should just do a
201+
build. */
202+
co_return amDone(substituterFailed ? ecFailed : ecNoSubstituters, g->ex);
203+
}
204+
205+
/* This should be set if the goal succeeded */
206+
assert(g->resolvedDrv);
207+
208+
if (g->resolvedDrvPath != drvPath) {
209+
/* Try everything again, now with a resolved derivation */
210+
auto bt2 = worker.makeBuildTraceGoal({
211+
makeConstantStorePathRef(g->resolvedDrvPath),
212+
id2.outputName,
213+
});
214+
215+
/* No longer need 'g' */
216+
g = nullptr;
217+
218+
co_await await(Goals{bt2});
219+
220+
/* Set the build trace value as our own. Note the signure will not
221+
match our key since we're the unresolved derivation, but that's
222+
fine. We're not writing it to the DB; that's `bt2`' job. */
223+
if (bt2->outputInfo)
224+
outputInfo = bt2->outputInfo;
225+
226+
co_return amDone(bt2->exitCode, bt2->ex);
227+
} else {
228+
/* None left. Terminate this goal and let someone else deal
229+
with it. */
230+
debug("build trace is not known for '%s', derivation is already resolved", id2.to_string());
231+
232+
if (substituterFailed) {
233+
worker.failedSubstitutions++;
234+
worker.updateProgress();
235+
}
236+
237+
/* Hack: don't indicate failure if there were no substituters.
238+
In that case the calling derivation should just do a
239+
build. */
240+
co_return amDone(
241+
substituterFailed ? ecFailed : ecNoSubstituters,
242+
Error("build trace is not known for '%s', derivation is already resolved", id2.to_string()));
243+
}
244+
}
245+
246+
std::string BuildTraceGoal::key()
247+
{
248+
/* "a$" ensures substitution goals happen before derivation
249+
goals. */
250+
return "a$" + std::string(id.to_string(worker.store));
251+
}
252+
253+
void BuildTraceGoal::handleEOF(Descriptor fd)
254+
{
255+
worker.wakeUp(shared_from_this());
256+
}
257+
258+
}

src/libstore/build/derivation-goal.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ std::string DerivationGoal::key()
5050
i.e. a derivation named "aardvark" always comes before
5151
"baboon". And substitution goals always happen before
5252
derivation goals (due to "b$"). */
53-
return "b$" + std::string(drvPath.name()) + "$" + SingleDerivedPath::Built{
53+
return "c$" + std::string(drvPath.name()) + "$" + SingleDerivedPath::Built{
5454
.drvPath = makeConstantStorePathRef(drvPath),
5555
.output = wantedOutput,
5656
}.to_string(worker.store);
@@ -170,6 +170,7 @@ Goal::Co DerivationGoal::haveDerivation()
170170
waitees.insert(
171171
upcast_goal(
172172
worker.makeDrvOutputSubstitutionGoal(
173+
drvPath,
173174
DrvOutput{status.outputHash, outputName},
174175
buildMode == bmRepair ? Repair : NoRepair
175176
)
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#include "nix/store/build/derivation-resolution-goal.hh"
2+
#include "nix/store/build/build-trace-goal.hh"
3+
#include "nix/util/finally.hh"
4+
#include "nix/store/build/worker.hh"
5+
#include "nix/store/build/substitution-goal.hh"
6+
#include "nix/util/callback.hh"
7+
#include "nix/store/derivations.hh"
8+
9+
namespace nix {
10+
11+
DerivationResolutionGoal::DerivationResolutionGoal(const StorePath & drvPath, Worker & worker)
12+
: Goal(worker, init())
13+
, drvPath(drvPath)
14+
{
15+
name = fmt("resolution of '%s'", worker.store.printStorePath(drvPath));
16+
trace("created");
17+
}
18+
19+
Goal::Co DerivationResolutionGoal::init()
20+
{
21+
trace("init");
22+
23+
std::unique_ptr<Derivation> drv;
24+
25+
if (worker.evalStore.isValidPath(drvPath)) {
26+
drv = std::make_unique<Derivation>(worker.evalStore.readDerivation(drvPath));
27+
} else if (worker.store.isValidPath(drvPath)) {
28+
drv = std::make_unique<Derivation>(worker.store.readDerivation(drvPath));
29+
} else {
30+
auto goal0 = worker.makePathSubstitutionGoal(drvPath);
31+
goal0->preserveException = true;
32+
co_await await(Goals{goal0});
33+
if (nrFailed > 0)
34+
co_return amDone(goal0->exitCode, goal0->ex);
35+
36+
drv = std::make_unique<Derivation>(worker.store.readDerivation(drvPath));
37+
}
38+
39+
trace("output path substituted");
40+
41+
std::map<SingleDerivedPath::Built, std::shared_ptr<BuildTraceGoal>> traceGoals;
42+
43+
{
44+
Goals waitees;
45+
46+
std::function<void(ref<const SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
47+
48+
accumInputPaths = [&](ref<const SingleDerivedPath> depDrvPath,
49+
const DerivedPathMap<StringSet>::ChildNode & inputNode) {
50+
for (auto & outputName : inputNode.value) {
51+
SingleDerivedPath::Built key{depDrvPath, outputName};
52+
auto goal = worker.makeBuildTraceGoal(key);
53+
waitees.insert(goal);
54+
traceGoals.insert_or_assign(std::move(key), std::move(goal));
55+
}
56+
57+
for (auto & [outputName, childNode] : inputNode.childMap)
58+
accumInputPaths(
59+
make_ref<const SingleDerivedPath>(SingleDerivedPath::Built{depDrvPath, outputName}), childNode);
60+
};
61+
62+
for (auto & [depDrvPath, depNode] : drv->inputDrvs.map)
63+
accumInputPaths(makeConstantStorePathRef(depDrvPath), depNode);
64+
65+
co_await await(std::move(waitees));
66+
}
67+
68+
if (nrFailed > 0) {
69+
co_return amDone(
70+
nrNoSubstituters > 0 ? ecNoSubstituters : ecFailed,
71+
Error("Could not find traces for all inputs for derivation '%s'", worker.store.printStorePath(drvPath)));
72+
}
73+
74+
trace("derivation inputs resolved");
75+
76+
if (auto d = drv->tryResolve(
77+
worker.store,
78+
[&](ref<const SingleDerivedPath> depDrvPath, const std::string & outputName) -> std::optional<StorePath> {
79+
auto mEntry = get(traceGoals, SingleDerivedPath::Built{depDrvPath, outputName});
80+
assert(mEntry);
81+
assert(*mEntry);
82+
auto outputInfo = (*mEntry)->outputInfo;
83+
assert(outputInfo);
84+
return outputInfo->outPath;
85+
})) {
86+
resolvedDrv = std::make_shared<BasicDerivation>(std::move(*d));
87+
resolvedDrvPath = writeDerivation(worker.store, Derivation{*resolvedDrv}, NoRepair, true);
88+
89+
trace("finished");
90+
co_return amDone(ecSuccess);
91+
} else {
92+
/* If we succesfully resolved all build trace goals, `tryResolve` should succeed. */
93+
assert(false);
94+
}
95+
}
96+
97+
std::string DerivationResolutionGoal::key()
98+
{
99+
/* "a$" ensures substitution goals happen before derivation
100+
goals. */
101+
return "b$" + worker.store.printStorePath(drvPath);
102+
}
103+
104+
void DerivationResolutionGoal::handleEOF(Descriptor fd)
105+
{
106+
worker.wakeUp(shared_from_this());
107+
}
108+
109+
}

0 commit comments

Comments
 (0)