Skip to content

Commit 37fca66

Browse files
Ericson2314roberth
andcommitted
Make KeyedBuildResult, BuildResult like before, and fix bug another way
In NixOS#6311 (comment), I realized since derivation goals' wanted outputs can "grow" due to overlapping dependencies (See `DerivationGoal::addWantedOutputs`, called by `Worker::makeDerivationGoalCommon`), the previous bug fix had an unfortunate side effect of causing more pointless rebuilds. In paticular, we have this situation: 1. Goal made from `DerivedPath::Built { foo, {a} }`. 2. Goal gives on on substituting, starts building. 3. Goal made from `DerivedPath::Built { foo, {b} }`, in fact is just modified original goal. 4. Though the goal had gotten as far as building, so all outputs were going to be produced, `addWantedOutputs` no longer knows that and so the goal is flagged to be restarted. This might sound far-fetched with input-addressed drvs, where we usually basically have all our goals "planned out" before we start doing anything, but with CA derivation goals and especially RFC 92, where *drv resolution* means goals are created after some building is completed, it is more likely to happen. So the first thing to do was restore the clearing of `wantedOutputs` we used to do, and then filter the outputs in `buildPathsWithResults` to only get the ones we care about. But fix also has its own side effect in that the `DerivedPath` in the `BuildResult` in `DerivationGoal` cannot be trusted; it is merely the *first* `DerivedPath` for which this goal was originally created. To remedy this, I made `BuildResult` be like it was before, and instead made `KeyedBuildResult` be a subclass wit the path. Only `buildPathsWithResults` returns `KeyedBuildResult`s, everything else just becomes like it was before, where the "key" is unambiguous from context. I think separating the "primary key" field(s) from the other fields is good practical in general anyways. (I would like to do the same thing for `ValidPathInfo`.) Among other things, it allows constructions like `std::map<Key, ThingWithKey>` where doesn't contain duplicate keys and just precludes the possibility of those duplicate keys being out of sync. We might leverage the above someday to overload `buildPathsWithResults` to take a *set* of return a *map* per the above. ----- Unfortunately, we need to avoid C++20 strictness on designated initializers. (BTW https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2287r1.html this offers some new syntax for this use-case. Hopefully this will be adopted and we can eventually use it.) No having that yet, maybe it would be better to not make `KeyedBuildResult` a subclass to just avoid this. Co-authored-by: Robert Hensing <[email protected]>
1 parent 9df7f3f commit 37fca66

File tree

11 files changed

+130
-51
lines changed

11 files changed

+130
-51
lines changed

src/libstore/build-result.hh

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,6 @@ struct BuildResult
8383
*/
8484
bool isNonDeterministic = false;
8585

86-
/**
87-
* The derivation we built or the store path we substituted.
88-
*/
89-
DerivedPath path;
90-
9186
/**
9287
* For derivations, a mapping from the names of the wanted outputs
9388
* to actual paths.
@@ -116,4 +111,15 @@ struct BuildResult
116111
}
117112
};
118113

114+
/**
115+
* A `BuildResult` together with its "primary key".
116+
*/
117+
struct KeyedBuildResult : BuildResult
118+
{
119+
/**
120+
* The derivation we built or the store path we substituted.
121+
*/
122+
DerivedPath path;
123+
};
124+
119125
}

src/libstore/build/derivation-goal.cc

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,10 @@ void DerivationGoal::outputsSubstitutionTried()
330330
produced using a substitute. So we have to build instead. */
331331
void DerivationGoal::gaveUpOnSubstitution()
332332
{
333+
/* Make sure checkPathValidity() from now on checks all
334+
outputs. */
335+
wantedOutputs = OutputsSpec::All { };
336+
333337
/* The inputs must be built before we can build this goal. */
334338
inputDrvOutputs.clear();
335339
if (useDerivation)
@@ -570,8 +574,6 @@ void DerivationGoal::inputsRealised()
570574
build hook. */
571575
state = &DerivationGoal::tryToBuild;
572576
worker.wakeUp(shared_from_this());
573-
574-
buildResult = BuildResult { .path = buildResult.path };
575577
}
576578

577579
void DerivationGoal::started()
@@ -1452,12 +1454,28 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
14521454
{
14531455
Goal::waiteeDone(waitee, result);
14541456

1455-
if (waitee->buildResult.success())
1456-
if (auto bfd = std::get_if<DerivedPath::Built>(&waitee->buildResult.path))
1457-
for (auto & [output, realisation] : waitee->buildResult.builtOutputs)
1457+
if (!useDerivation) return;
1458+
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
1459+
1460+
auto * dg = dynamic_cast<DerivationGoal *>(&*waitee);
1461+
if (!dg) return;
1462+
1463+
auto outputs = fullDrv.inputDrvs.find(dg->drvPath);
1464+
if (outputs == fullDrv.inputDrvs.end()) return;
1465+
1466+
for (auto & outputName : outputs->second) {
1467+
auto buildResult = dg->getBuildResult(DerivedPath::Built {
1468+
.drvPath = dg->drvPath,
1469+
.outputs = OutputsSpec::Names { outputName },
1470+
});
1471+
if (buildResult.success()) {
1472+
for (auto & [output, realisation] : buildResult.builtOutputs) {
14581473
inputDrvOutputs.insert_or_assign(
1459-
{ bfd->drvPath, output.outputName },
1474+
{ dg->drvPath, output.outputName },
14601475
realisation.outPath);
1476+
}
1477+
}
1478+
}
14611479
}
14621480

14631481
}

src/libstore/build/entry-points.cc

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,31 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
3939
}
4040
}
4141

42-
std::vector<BuildResult> Store::buildPathsWithResults(
42+
std::vector<KeyedBuildResult> Store::buildPathsWithResults(
4343
const std::vector<DerivedPath> & reqs,
4444
BuildMode buildMode,
4545
std::shared_ptr<Store> evalStore)
4646
{
4747
Worker worker(*this, evalStore ? *evalStore : *this);
4848

4949
Goals goals;
50-
for (const auto & br : reqs)
51-
goals.insert(worker.makeGoal(br, buildMode));
50+
std::vector<std::pair<const DerivedPath &, GoalPtr>> state;
51+
52+
for (const auto & req : reqs) {
53+
auto goal = worker.makeGoal(req, buildMode);
54+
goals.insert(goal);
55+
state.push_back({req, goal});
56+
}
5257

5358
worker.run(goals);
5459

55-
std::vector<BuildResult> results;
60+
std::vector<KeyedBuildResult> results;
5661

57-
for (auto & i : goals)
58-
results.push_back(i->buildResult);
62+
for (auto & [req, goalPtr] : state)
63+
results.emplace_back(KeyedBuildResult {
64+
goalPtr->getBuildResult(req),
65+
/* .path = */ req,
66+
});
5967

6068
return results;
6169
}
@@ -68,15 +76,14 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
6876

6977
try {
7078
worker.run(Goals{goal});
71-
return goal->buildResult;
79+
return goal->getBuildResult(DerivedPath::Built {
80+
.drvPath = drvPath,
81+
.outputs = OutputsSpec::All {},
82+
});
7283
} catch (Error & e) {
7384
return BuildResult {
7485
.status = BuildResult::MiscFailure,
7586
.errorMsg = e.msg(),
76-
.path = DerivedPath::Built {
77-
.drvPath = drvPath,
78-
.outputs = OutputsSpec::All { },
79-
},
8087
};
8188
};
8289
}

src/libstore/build/goal.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,29 @@ bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
1111
}
1212

1313

14+
BuildResult Goal::getBuildResult(const DerivedPath & req) {
15+
BuildResult res { buildResult };
16+
17+
if (auto pbp = std::get_if<DerivedPath::Built>(&req)) {
18+
auto & bp = *pbp;
19+
20+
/* Because goals are in general shared between derived paths
21+
that share the same derivation, we need to filter their
22+
results to get back just the results we care about.
23+
*/
24+
25+
for (auto it = res.builtOutputs.begin(); it != res.builtOutputs.end();) {
26+
if (bp.outputs.contains(it->first.outputName))
27+
++it;
28+
else
29+
it = res.builtOutputs.erase(it);
30+
}
31+
}
32+
33+
return res;
34+
}
35+
36+
1437
void addToWeakGoals(WeakGoals & goals, GoalPtr p)
1538
{
1639
if (goals.find(p) != goals.end())

src/libstore/build/goal.hh

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,33 @@ struct Goal : public std::enable_shared_from_this<Goal>
8181
*/
8282
ExitCode exitCode = ecBusy;
8383

84+
protected:
8485
/**
8586
* Build result.
8687
*/
8788
BuildResult buildResult;
8889

90+
public:
91+
92+
/**
93+
* Project a `BuildResult` with just the information that pertains
94+
* to the given request.
95+
*
96+
* In general, goals may be aliased between multiple requests, and
97+
* the stored `BuildResult` has information for the union of all
98+
* requests. We don't want to leak what the other request are for
99+
* sake of both privacy and determinism, and this "safe accessor"
100+
* ensures we don't.
101+
*/
102+
BuildResult getBuildResult(const DerivedPath &);
103+
89104
/**
90105
* Exception containing an error message, if any.
91106
*/
92107
std::optional<Error> ex;
93108

94109
Goal(Worker & worker, DerivedPath path)
95110
: worker(worker)
96-
, buildResult { .path = std::move(path) }
97111
{ }
98112

99113
virtual ~Goal()

src/libstore/build/local-derivation-goal.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1335,7 +1335,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
13351335
result.rethrow();
13361336
}
13371337

1338-
std::vector<BuildResult> buildPathsWithResults(
1338+
std::vector<KeyedBuildResult> buildPathsWithResults(
13391339
const std::vector<DerivedPath> & paths,
13401340
BuildMode buildMode = bmNormal,
13411341
std::shared_ptr<Store> evalStore = nullptr) override

src/libstore/legacy-ssh-store.cc

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -287,12 +287,7 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
287287

288288
conn->to.flush();
289289

290-
BuildResult status {
291-
.path = DerivedPath::Built {
292-
.drvPath = drvPath,
293-
.outputs = OutputsSpec::All { },
294-
},
295-
};
290+
BuildResult status;
296291
status.status = (BuildResult::Status) readInt(conn->from);
297292
conn->from >> status.errorMsg;
298293

@@ -330,7 +325,7 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
330325

331326
conn->to.flush();
332327

333-
BuildResult result { .path = DerivedPath::Opaque { StorePath::dummy } };
328+
BuildResult result;
334329
result.status = (BuildResult::Status) readInt(conn->from);
335330

336331
if (!result.success()) {

src/libstore/remote-store.cc

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,26 @@ void write(const Store & store, Sink & out, const DrvOutput & drvOutput)
125125
}
126126

127127

128-
BuildResult read(const Store & store, Source & from, Phantom<BuildResult> _)
128+
KeyedBuildResult read(const Store & store, Source & from, Phantom<KeyedBuildResult> _)
129129
{
130130
auto path = worker_proto::read(store, from, Phantom<DerivedPath> {});
131-
BuildResult res { .path = path };
131+
auto br = worker_proto::read(store, from, Phantom<BuildResult> {});
132+
return KeyedBuildResult {
133+
std::move(br),
134+
/* .path = */ std::move(path),
135+
};
136+
}
137+
138+
void write(const Store & store, Sink & to, const KeyedBuildResult & res)
139+
{
140+
worker_proto::write(store, to, res.path);
141+
worker_proto::write(store, to, static_cast<const BuildResult &>(res));
142+
}
143+
144+
145+
BuildResult read(const Store & store, Source & from, Phantom<BuildResult> _)
146+
{
147+
BuildResult res;
132148
res.status = (BuildResult::Status) readInt(from);
133149
from
134150
>> res.errorMsg
@@ -142,7 +158,6 @@ BuildResult read(const Store & store, Source & from, Phantom<BuildResult> _)
142158

143159
void write(const Store & store, Sink & to, const BuildResult & res)
144160
{
145-
worker_proto::write(store, to, res.path);
146161
to
147162
<< res.status
148163
<< res.errorMsg
@@ -865,7 +880,7 @@ void RemoteStore::buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMod
865880
readInt(conn->from);
866881
}
867882

868-
std::vector<BuildResult> RemoteStore::buildPathsWithResults(
883+
std::vector<KeyedBuildResult> RemoteStore::buildPathsWithResults(
869884
const std::vector<DerivedPath> & paths,
870885
BuildMode buildMode,
871886
std::shared_ptr<Store> evalStore)
@@ -880,7 +895,7 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
880895
writeDerivedPaths(*this, conn, paths);
881896
conn->to << buildMode;
882897
conn.processStderr();
883-
return worker_proto::read(*this, conn->from, Phantom<std::vector<BuildResult>> {});
898+
return worker_proto::read(*this, conn->from, Phantom<std::vector<KeyedBuildResult>> {});
884899
} else {
885900
// Avoid deadlock.
886901
conn_.reset();
@@ -889,21 +904,25 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
889904
// fails, but meh.
890905
buildPaths(paths, buildMode, evalStore);
891906

892-
std::vector<BuildResult> results;
907+
std::vector<KeyedBuildResult> results;
893908

894909
for (auto & path : paths) {
895910
std::visit(
896911
overloaded {
897912
[&](const DerivedPath::Opaque & bo) {
898-
results.push_back(BuildResult {
899-
.status = BuildResult::Substituted,
900-
.path = bo,
913+
results.push_back(KeyedBuildResult {
914+
{
915+
.status = BuildResult::Substituted,
916+
},
917+
/* .path = */ bo,
901918
});
902919
},
903920
[&](const DerivedPath::Built & bfd) {
904-
BuildResult res {
905-
.status = BuildResult::Built,
906-
.path = bfd,
921+
KeyedBuildResult res {
922+
{
923+
.status = BuildResult::Built
924+
},
925+
/* .path = */ bfd,
907926
};
908927

909928
OutputPathMap outputs;
@@ -952,12 +971,7 @@ BuildResult RemoteStore::buildDerivation(const StorePath & drvPath, const BasicD
952971
writeDerivation(conn->to, *this, drv);
953972
conn->to << buildMode;
954973
conn.processStderr();
955-
BuildResult res {
956-
.path = DerivedPath::Built {
957-
.drvPath = drvPath,
958-
.outputs = OutputsSpec::All { },
959-
},
960-
};
974+
BuildResult res;
961975
res.status = (BuildResult::Status) readInt(conn->from);
962976
conn->from >> res.errorMsg;
963977
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 29) {

src/libstore/remote-store.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public:
114114

115115
void buildPaths(const std::vector<DerivedPath> & paths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override;
116116

117-
std::vector<BuildResult> buildPathsWithResults(
117+
std::vector<KeyedBuildResult> buildPathsWithResults(
118118
const std::vector<DerivedPath> & paths,
119119
BuildMode buildMode,
120120
std::shared_ptr<Store> evalStore) override;

src/libstore/store-api.hh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ enum BuildMode { bmNormal, bmRepair, bmCheck };
9292
enum TrustedFlag : bool { NotTrusted = false, Trusted = true };
9393

9494
struct BuildResult;
95+
struct KeyedBuildResult;
9596

9697

9798
typedef std::map<StorePath, std::optional<ContentAddress>> StorePathCAMap;
@@ -575,7 +576,7 @@ public:
575576
* case of a build/substitution error, this function won't throw an
576577
* exception, but return a BuildResult containing an error message.
577578
*/
578-
virtual std::vector<BuildResult> buildPathsWithResults(
579+
virtual std::vector<KeyedBuildResult> buildPathsWithResults(
579580
const std::vector<DerivedPath> & paths,
580581
BuildMode buildMode = bmNormal,
581582
std::shared_ptr<Store> evalStore = nullptr);

src/libstore/worker-protocol.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ MAKE_WORKER_PROTO(, DerivedPath);
103103
MAKE_WORKER_PROTO(, Realisation);
104104
MAKE_WORKER_PROTO(, DrvOutput);
105105
MAKE_WORKER_PROTO(, BuildResult);
106+
MAKE_WORKER_PROTO(, KeyedBuildResult);
106107
MAKE_WORKER_PROTO(, std::optional<TrustedFlag>);
107108

108109
MAKE_WORKER_PROTO(template<typename T>, std::vector<T>);

0 commit comments

Comments
 (0)