Skip to content

Commit 9c0bcf9

Browse files
committed
Convert PathSubstitutionGoal to more idiomatic coroutine code
Uses the same style as DrvOutputSubstitutionGoal roughly.
1 parent 6f87de6 commit 9c0bcf9

File tree

5 files changed

+99
-169
lines changed

5 files changed

+99
-169
lines changed

src/libstore/build/drv-output-substitution-goal.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Goal::Co DrvOutputSubstitutionGoal::init()
3030

3131
auto subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
3232

33+
bool substituterFailed = false;
34+
3335
for (auto sub : subs) {
3436
trace("trying next substituter");
3537

src/libstore/build/drv-output-substitution-goal.hh

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ class DrvOutputSubstitutionGoal : public Goal {
2727
*/
2828
DrvOutput id;
2929

30-
/**
31-
* Whether a substituter failed.
32-
*/
33-
bool substituterFailed = false;
34-
3530
public:
3631
DrvOutputSubstitutionGoal(const DrvOutput& id, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
3732

src/libstore/build/goal.hh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public:
143143
* children you're waiting for.
144144
* Coroutines allow you to resume the work cleanly.
145145
*
146-
* @note Below follows a brief explanation of C++20 coroutines.
146+
* @note Brief explanation of C++20 coroutines:
147147
* When you `Co f()`, a `std::coroutine_handle<promise_type>` is created,
148148
* alongside its @ref promise_type.
149149
* There are suspension points at the beginning of the coroutine,
@@ -165,6 +165,8 @@ public:
165165
* @todo Allocate explicitly on stack since HALO thing doesn't really work,
166166
* specifically, there's no way to uphold the requirements when trying to do
167167
* tail-calls without using a trampoline AFAICT.
168+
*
169+
* @todo Support returning data natively
168170
*/
169171
struct [[nodiscard]] Co {
170172
/**

src/libstore/build/substitution-goal.cc

Lines changed: 91 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -52,132 +52,112 @@ Goal::Co PathSubstitutionGoal::init()
5252
if (settings.readOnlyMode)
5353
throw Error("cannot substitute path '%s' - no write access to the Nix store", worker.store.printStorePath(storePath));
5454

55-
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
55+
auto subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
5656

57-
trace("calling tryNext");
57+
bool substituterFailed = false;
5858

59-
co_return tryNext();
60-
}
59+
for (auto sub : subs) {
60+
trace("trying next substituter");
6161

62+
cleanup();
6263

63-
Goal::Co PathSubstitutionGoal::tryNext()
64-
{
65-
trace("trying next substituter");
64+
/* The path the substituter refers to the path as. This will be
65+
* different when the stores have different names. */
66+
std::optional<StorePath> subPath;
6667

67-
cleanup();
68+
/* Path info returned by the substituter's query info operation. */
69+
std::shared_ptr<const ValidPathInfo> info;
6870

69-
if (subs.size() == 0) {
70-
/* None left. Terminate this goal and let someone else deal
71-
with it. */
71+
if (ca) {
72+
subPath = sub->makeFixedOutputPathFromCA(
73+
std::string { storePath.name() },
74+
ContentAddressWithReferences::withoutRefs(*ca));
75+
if (sub->storeDir == worker.store.storeDir)
76+
assert(subPath == storePath);
77+
} else if (sub->storeDir != worker.store.storeDir) {
78+
continue;
79+
}
7280

73-
if (substituterFailed) {
74-
worker.failedSubstitutions++;
75-
worker.updateProgress();
81+
try {
82+
// FIXME: make async
83+
info = sub->queryPathInfo(subPath ? *subPath : storePath);
84+
} catch (InvalidPath &) {
85+
continue;
86+
} catch (SubstituterDisabled & e) {
87+
if (settings.tryFallback) continue;
88+
else throw e;
89+
} catch (Error & e) {
90+
if (settings.tryFallback) {
91+
logError(e.info());
92+
continue;
93+
} else throw e;
7694
}
7795

78-
/* Hack: don't indicate failure if there were no substituters.
79-
In that case the calling derivation should just do a
80-
build. */
81-
co_return done(
82-
substituterFailed ? ecFailed : ecNoSubstituters,
83-
BuildResult::NoSubstituters,
84-
fmt("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath)));
85-
}
96+
if (info->path != storePath) {
97+
if (info->isContentAddressed(*sub) && info->references.empty()) {
98+
auto info2 = std::make_shared<ValidPathInfo>(*info);
99+
info2->path = storePath;
100+
info = info2;
101+
} else {
102+
printError("asked '%s' for '%s' but got '%s'",
103+
sub->getUri(), worker.store.printStorePath(storePath), sub->printStorePath(info->path));
104+
continue;
105+
}
106+
}
86107

87-
sub = subs.front();
88-
subs.pop_front();
89-
90-
if (ca) {
91-
subPath = sub->makeFixedOutputPathFromCA(
92-
std::string { storePath.name() },
93-
ContentAddressWithReferences::withoutRefs(*ca));
94-
if (sub->storeDir == worker.store.storeDir)
95-
assert(subPath == storePath);
96-
} else if (sub->storeDir != worker.store.storeDir) {
97-
co_return tryNext();
98-
}
108+
/* Update the total expected download size. */
109+
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
99110

100-
// Horrible, horrible code.
101-
// Needed because we can't `co_*` inside a catch-clause.
102-
// `std::variant` would be cleaner perhaps.
103-
int i = 0;
104-
std::optional<Error> e;
105-
try {
106-
// FIXME: make async
107-
info = sub->queryPathInfo(subPath ? *subPath : storePath);
108-
} catch (InvalidPath &) {
109-
i = 1;
110-
} catch (SubstituterDisabled & e_) {
111-
i = 2;
112-
e = e_;
113-
} catch (Error & e_) {
114-
i = 3;
115-
e = e_;
116-
}
117-
switch (i) {
118-
case 0:
119-
break;
120-
case 1:
121-
co_return tryNext();
122-
case 2:
123-
if (settings.tryFallback) {
124-
co_return tryNext();
125-
}
126-
throw *e;
127-
case 3:
128-
if (settings.tryFallback) {
129-
logError(e->info());
130-
co_return tryNext();
131-
}
132-
throw *e;
133-
}
111+
maintainExpectedNar = std::make_unique<MaintainCount<uint64_t>>(worker.expectedNarSize, info->narSize);
134112

135-
if (info->path != storePath) {
136-
if (info->isContentAddressed(*sub) && info->references.empty()) {
137-
auto info2 = std::make_shared<ValidPathInfo>(*info);
138-
info2->path = storePath;
139-
info = info2;
140-
} else {
141-
printError("asked '%s' for '%s' but got '%s'",
142-
sub->getUri(), worker.store.printStorePath(storePath), sub->printStorePath(info->path));
143-
co_return tryNext();
144-
}
145-
}
113+
maintainExpectedDownload =
114+
narInfo && narInfo->fileSize
115+
? std::make_unique<MaintainCount<uint64_t>>(worker.expectedDownloadSize, narInfo->fileSize)
116+
: nullptr;
146117

147-
/* Update the total expected download size. */
148-
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
118+
worker.updateProgress();
149119

150-
maintainExpectedNar = std::make_unique<MaintainCount<uint64_t>>(worker.expectedNarSize, info->narSize);
120+
/* Bail out early if this substituter lacks a valid
121+
signature. LocalStore::addToStore() also checks for this, but
122+
only after we've downloaded the path. */
123+
if (!sub->isTrusted && worker.store.pathInfoIsUntrusted(*info))
124+
{
125+
warn("ignoring substitute for '%s' from '%s', as it's not signed by any of the keys in 'trusted-public-keys'",
126+
worker.store.printStorePath(storePath), sub->getUri());
127+
continue;
128+
}
151129

152-
maintainExpectedDownload =
153-
narInfo && narInfo->fileSize
154-
? std::make_unique<MaintainCount<uint64_t>>(worker.expectedDownloadSize, narInfo->fileSize)
155-
: nullptr;
130+
/* To maintain the closure invariant, we first have to realise the
131+
paths referenced by this one. */
132+
for (auto & i : info->references)
133+
if (i != storePath) /* ignore self-references */
134+
addWaitee(worker.makePathSubstitutionGoal(i));
156135

157-
worker.updateProgress();
136+
if (!waitees.empty()) co_await SuspendGoal{};
158137

159-
/* Bail out early if this substituter lacks a valid
160-
signature. LocalStore::addToStore() also checks for this, but
161-
only after we've downloaded the path. */
162-
if (!sub->isTrusted && worker.store.pathInfoIsUntrusted(*info))
163-
{
164-
warn("ignoring substitute for '%s' from '%s', as it's not signed by any of the keys in 'trusted-public-keys'",
165-
worker.store.printStorePath(storePath), sub->getUri());
166-
co_return tryNext();
138+
// FIXME: consider returning boolean instead of passing in reference
139+
bool out = false; // is mutated by tryToRun
140+
co_await tryToRun(subPath ? *subPath : storePath, sub, info, out);
141+
substituterFailed = substituterFailed || out;
167142
}
168143

169-
/* To maintain the closure invariant, we first have to realise the
170-
paths referenced by this one. */
171-
for (auto & i : info->references)
172-
if (i != storePath) /* ignore self-references */
173-
addWaitee(worker.makePathSubstitutionGoal(i));
144+
/* None left. Terminate this goal and let someone else deal
145+
with it. */
146+
147+
worker.failedSubstitutions++;
148+
worker.updateProgress();
174149

175-
if (!waitees.empty()) co_await SuspendGoal{};
176-
co_return referencesValid();
150+
/* Hack: don't indicate failure if there were no substituters.
151+
In that case the calling derivation should just do a
152+
build. */
153+
co_return done(
154+
substituterFailed ? ecFailed : ecNoSubstituters,
155+
BuildResult::NoSubstituters,
156+
fmt("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath)));
177157
}
178158

179159

180-
Goal::Co PathSubstitutionGoal::referencesValid()
160+
Goal::Co PathSubstitutionGoal::tryToRun(StorePath subPath, nix::ref<Store> sub, std::shared_ptr<const ValidPathInfo> info, bool& substituterFailed)
181161
{
182162
trace("all references realised");
183163

@@ -194,12 +174,7 @@ Goal::Co PathSubstitutionGoal::referencesValid()
194174

195175
worker.wakeUp(shared_from_this());
196176
co_await SuspendGoal{};
197-
co_return tryToRun();
198-
}
199177

200-
201-
Goal::Co PathSubstitutionGoal::tryToRun()
202-
{
203178
trace("trying to run");
204179

205180
/* Make sure that we are allowed to start a substitution. Note that even
@@ -210,7 +185,7 @@ Goal::Co PathSubstitutionGoal::tryToRun()
210185
co_await SuspendGoal{};
211186
}
212187

213-
maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
188+
auto maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
214189
worker.updateProgress();
215190

216191
#ifndef _WIN32
@@ -219,9 +194,9 @@ Goal::Co PathSubstitutionGoal::tryToRun()
219194
outPipe.createAsyncPipe(worker.ioport.get());
220195
#endif
221196

222-
promise = std::promise<void>();
197+
auto promise = std::promise<void>();
223198

224-
thr = std::thread([this]() {
199+
thr = std::thread([this, &promise, &subPath, &sub]() {
225200
try {
226201
ReceiveInterrupts receiveInterrupts;
227202

@@ -232,7 +207,7 @@ Goal::Co PathSubstitutionGoal::tryToRun()
232207
PushActivity pact(act.id);
233208

234209
copyStorePath(*sub, worker.store,
235-
subPath ? *subPath : storePath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs);
210+
subPath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs);
236211

237212
promise.set_value();
238213
} catch (...) {
@@ -249,12 +224,7 @@ Goal::Co PathSubstitutionGoal::tryToRun()
249224
}, true, false);
250225

251226
co_await SuspendGoal{};
252-
co_return finished();
253-
}
254-
255227

256-
Goal::Co PathSubstitutionGoal::finished()
257-
{
258228
trace("substitute finished");
259229

260230
thr.join();
@@ -276,8 +246,7 @@ Goal::Co PathSubstitutionGoal::finished()
276246
substituterFailed = true;
277247
}
278248

279-
/* Try the next substitute. */
280-
co_return tryNext();
249+
co_return Return{};
281250
}
282251

283252
worker.markContentsGood(storePath);
@@ -295,6 +264,7 @@ Goal::Co PathSubstitutionGoal::finished()
295264
worker.doneDownloadSize += fileSize;
296265
}
297266

267+
assert(maintainExpectedNar);
298268
worker.doneNarSize += maintainExpectedNar->delta;
299269
maintainExpectedNar.reset();
300270

@@ -304,14 +274,12 @@ Goal::Co PathSubstitutionGoal::finished()
304274
}
305275

306276

307-
void PathSubstitutionGoal::handleChildOutput(Descriptor fd, std::string_view data)
308-
{
309-
}
277+
void PathSubstitutionGoal::handleChildOutput(Descriptor fd, std::string_view data) {}
310278

311279

312280
void PathSubstitutionGoal::handleEOF(Descriptor fd)
313281
{
314-
if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
282+
worker.wakeUp(shared_from_this());
315283
}
316284

317285

0 commit comments

Comments
 (0)