aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libstore/normalise.cc272
1 files changed, 214 insertions, 58 deletions
diff --git a/src/libstore/normalise.cc b/src/libstore/normalise.cc
index cbb0e2f75..2e7c87ade 100644
--- a/src/libstore/normalise.cc
+++ b/src/libstore/normalise.cc
@@ -48,11 +48,17 @@ protected:
/* Number of goals we are waiting for. */
unsigned int nrWaitees;
-
+ /* Number of goals we were waiting for that have failed. */
+ unsigned int nrFailed;
+
+ /* Whether amDone() has been called. */
+ bool done;
+
+
Goal(Worker & _worker) : worker(_worker)
{
- nrWaitees = 0;
+ done = false;
}
virtual ~Goal()
@@ -60,6 +66,12 @@ protected:
printMsg(lvlVomit, "goal destroyed");
}
+ void resetWaitees(int nrWaitees)
+ {
+ this->nrWaitees = nrWaitees;
+ nrFailed = 0;
+ }
+
public:
virtual void work() = 0;
@@ -70,9 +82,14 @@ public:
void addWaiter(GoalPtr waiter);
- void waiteeDone();
+ virtual void waiteeDone(bool success);
+
+protected:
+ virtual void waiteeFailed()
+ {
+ }
- void amDone();
+ void amDone(bool success = true);
};
@@ -160,6 +177,13 @@ public:
};
+class BuildError : public Error
+{
+public:
+ BuildError(const format & f) : Error(f) { };
+};
+
+
//////////////////////////////////////////////////////////////////////
@@ -170,18 +194,25 @@ void Goal::addWaiter(GoalPtr waiter)
}
-void Goal::waiteeDone()
+void Goal::waiteeDone(bool success)
{
assert(nrWaitees > 0);
+ /* Note: waiteeFailed should never call amDone()! */
+ if (!success) {
+ ++nrFailed;
+ waiteeFailed();
+ }
if (!--nrWaitees) worker.wakeUp(shared_from_this());
}
-void Goal::amDone()
+void Goal::amDone(bool success)
{
printMsg(lvlVomit, "done");
+ assert(!done);
+ done = true;
for (Goals::iterator i = waiters.begin(); i != waiters.end(); ++i)
- (*i)->waiteeDone();
+ (*i)->waiteeDone(success);
worker.removeGoal(shared_from_this());
}
@@ -375,7 +406,7 @@ void NormalisationGoal::init()
/* The first thing to do is to make sure that the store expression
exists. If it doesn't, it may be created through a
substitute. */
- nrWaitees = 1;
+ resetWaitees(1);
worker.addSubstitutionGoal(nePath, shared_from_this());
state = &NormalisationGoal::haveStoreExpr;
@@ -386,6 +417,14 @@ void NormalisationGoal::haveStoreExpr()
{
trace("loading store expression");
+ if (nrFailed != 0) {
+ printMsg(lvlError,
+ format("cannot normalise missing store expression `%1%'")
+ % nePath);
+ amDone(false);
+ return;
+ }
+
assert(isValidPath(nePath));
/* Get the store expression. */
@@ -403,7 +442,7 @@ void NormalisationGoal::haveStoreExpr()
i != expr.derivation.inputs.end(); ++i)
worker.addNormalisationGoal(*i, shared_from_this());
- nrWaitees = expr.derivation.inputs.size();
+ resetWaitees(expr.derivation.inputs.size());
state = &NormalisationGoal::inputNormalised;
}
@@ -413,6 +452,15 @@ void NormalisationGoal::inputNormalised()
{
trace("all inputs normalised");
+ if (nrFailed != 0) {
+ printMsg(lvlError,
+ format("cannot normalise derivation `%1%': "
+ "%2% closure element(s) could not be normalised")
+ % nePath % nrFailed);
+ amDone(false);
+ return;
+ }
+
/* Inputs must also be realised before we can build this goal. */
for (PathSet::iterator i = expr.derivation.inputs.begin();
i != expr.derivation.inputs.end(); ++i)
@@ -424,7 +472,7 @@ void NormalisationGoal::inputNormalised()
worker.addRealisationGoal(neInput, shared_from_this());
}
- nrWaitees = expr.derivation.inputs.size();
+ resetWaitees(expr.derivation.inputs.size());
state = &NormalisationGoal::inputRealised;
}
@@ -434,6 +482,15 @@ void NormalisationGoal::inputRealised()
{
trace("all inputs realised");
+ if (nrFailed != 0) {
+ printMsg(lvlError,
+ format("cannot normalise derivation `%1%': "
+ "%2% closure element(s) could not be realised")
+ % nePath % nrFailed);
+ amDone(false);
+ return;
+ }
+
/* Okay, try to build. Note that here we don't wait for a build
slot to become available, since we don't need one if there is a
build hook. */
@@ -446,42 +503,50 @@ void NormalisationGoal::tryToBuild()
{
trace("trying to build");
- /* Is the build hook willing to accept this job? */
- switch (tryBuildHook()) {
- case rpAccept:
- /* Yes, it has started doing so. Wait until we get
- EOF from the hook. */
- state = &NormalisationGoal::buildDone;
- return;
- case rpPostpone:
- /* Not now; wait until at least one child finishes. */
+ try {
+
+ /* Is the build hook willing to accept this job? */
+ switch (tryBuildHook()) {
+ case rpAccept:
+ /* Yes, it has started doing so. Wait until we get
+ EOF from the hook. */
+ state = &NormalisationGoal::buildDone;
+ return;
+ case rpPostpone:
+ /* Not now; wait until at least one child finishes. */
+ worker.waitForBuildSlot(shared_from_this());
+ return;
+ case rpDecline:
+ /* We should do it ourselves. */
+ break;
+ case rpDone:
+ /* Somebody else did it (there is a successor now). */
+ amDone();
+ return;
+ }
+
+ /* Make sure that we are allowed to start a build. */
+ if (!worker.canBuildMore()) {
worker.waitForBuildSlot(shared_from_this());
return;
- case rpDecline:
- /* We should do it ourselves. */
- break;
- case rpDone:
- /* Somebody else did it (there is a successor now). */
+ }
+
+ /* Acquire locks and such. If we then see that there now is a
+ successor, we're done. */
+ if (!prepareBuild()) {
amDone();
return;
- }
+ }
- /* Make sure that we are allowed to start a build. */
- if (!worker.canBuildMore()) {
- worker.waitForBuildSlot(shared_from_this());
- return;
- }
+ /* Okay, we have to build. */
+ startBuilder();
- /* Acquire locks and such. If we then see that there now is a
- successor, we're done. */
- if (!prepareBuild()) {
- amDone();
+ } catch (BuildError & e) {
+ printMsg(lvlError, e.msg());
+ amDone(false);
return;
}
- /* Okay, we have to build. */
- startBuilder();
-
/* This state will be reached when we get EOF on the child's
log pipe. */
state = &NormalisationGoal::buildDone;
@@ -514,15 +579,23 @@ void NormalisationGoal::buildDone()
/* Check the exit status. */
if (!statusOk(status)) {
deleteTmpDir(false);
- throw Error(format("builder for `%1%' %2%")
+ printMsg(lvlError, format("builder for `%1%' %2%")
% nePath % statusToString(status));
+ amDone(false);
+ return;
}
deleteTmpDir(true);
/* Compute a closure store expression, and register it as our
successor. */
- createClosure();
+ try {
+ createClosure();
+ } catch (BuildError & e) {
+ printMsg(lvlError, e.msg());
+ amDone(false);
+ return;
+ }
amDone();
}
@@ -791,8 +864,9 @@ void NormalisationGoal::startBuilder()
/* Right platform? */
if (expr.derivation.platform != thisSystem)
- throw Error(format("a `%1%' is required, but I am a `%2%'")
- % expr.derivation.platform % thisSystem);
+ throw BuildError(
+ format("a `%1%' is required to build `%3%', but I am a `%2%'")
+ % expr.derivation.platform % thisSystem % nePath);
/* If any of the outputs already exist but are not registered,
delete them. */
@@ -923,8 +997,11 @@ void NormalisationGoal::createClosure()
i != expr.derivation.outputs.end(); ++i)
{
Path path = *i;
- if (!pathExists(path))
- throw Error(format("output path `%1%' does not exist") % path);
+ if (!pathExists(path)) {
+ throw BuildError(
+ format("builder for `%1%' failed to produce output path `%1%'")
+ % nePath % path);
+ }
nf.closure.roots.insert(path);
makePathReadOnly(path);
@@ -1038,18 +1115,18 @@ void NormalisationGoal::initChild()
commonChildInit(logPipe);
if (chdir(tmpDir.c_str()) == -1)
- throw SysError(format("changing into to `%1%'") % tmpDir);
+ throw SysError(format("changing into `%1%'") % tmpDir);
/* When running a hook, dup the communication pipes. */
bool inHook = fromHook.writeSide.isOpen();
if (inHook) {
fromHook.readSide.close();
if (dup2(fromHook.writeSide, 3) == -1)
- throw SysError("dup1");
+ throw SysError("dupping from-hook write side");
toHook.writeSide.close();
if (dup2(toHook.readSide, 4) == -1)
- throw SysError("dup2");
+ throw SysError("dupping to-hook read side");
}
/* Close all other file descriptors. */
@@ -1139,7 +1216,7 @@ void RealisationGoal::init()
/* The first thing to do is to make sure that the store expression
exists. If it doesn't, it may be created through a
substitute. */
- nrWaitees = 1;
+ resetWaitees(1);
worker.addSubstitutionGoal(nePath, shared_from_this());
state = &RealisationGoal::haveStoreExpr;
@@ -1150,6 +1227,14 @@ void RealisationGoal::haveStoreExpr()
{
trace("loading store expression");
+ if (nrFailed != 0) {
+ printMsg(lvlError,
+ format("cannot realise missing store expression `%1%'")
+ % nePath);
+ amDone(false);
+ return;
+ }
+
assert(isValidPath(nePath));
/* Get the store expression. */
@@ -1165,7 +1250,7 @@ void RealisationGoal::haveStoreExpr()
i != expr.closure.elems.end(); ++i)
worker.addSubstitutionGoal(i->first, shared_from_this());
- nrWaitees = expr.closure.elems.size();
+ resetWaitees(expr.closure.elems.size());
state = &RealisationGoal::elemFinished;
}
@@ -1175,6 +1260,16 @@ void RealisationGoal::elemFinished()
{
trace("all closure elements present");
+ if (nrFailed != 0) {
+ printMsg(lvlError,
+ format("cannot realise closure `%1%': "
+ "%2% closure element(s) are not present "
+ "and could not be substituted")
+ % nePath % nrFailed);
+ amDone(false);
+ return;
+ }
+
amDone();
}
@@ -1269,15 +1364,21 @@ void SubstitutionGoal::tryNext()
{
trace("trying next substitute");
- if (subs.size() == 0) throw Error(
- format("path `%1%' is required, but it has no (remaining) substitutes")
+ if (subs.size() == 0) {
+ /* None left. Terminate this goal and let someone else deal
+ with it. */
+ printMsg(lvlError,
+ format("path `%1%' is required, but it has no (remaining) substitutes")
% storePath);
+ amDone(false);
+ return;
+ }
sub = subs.front();
subs.pop_front();
/* Normalise the substitute store expression. */
worker.addNormalisationGoal(sub.storeExpr, shared_from_this());
- nrWaitees = 1;
+ resetWaitees(1);
state = &SubstitutionGoal::exprNormalised;
}
@@ -1287,11 +1388,17 @@ void SubstitutionGoal::exprNormalised()
{
trace("substitute store expression normalised");
+ if (nrFailed != 0) {
+ tryNext();
+ return;
+ }
+
/* Realise the substitute store expression. */
if (!querySuccessor(sub.storeExpr, nfSub))
nfSub = sub.storeExpr;
worker.addRealisationGoal(nfSub, shared_from_this());
- nrWaitees = 1;
+
+ resetWaitees(1);
state = &SubstitutionGoal::exprRealised;
}
@@ -1301,6 +1408,11 @@ void SubstitutionGoal::exprRealised()
{
trace("substitute store expression realised");
+ if (nrFailed != 0) {
+ tryNext();
+ return;
+ }
+
state = &SubstitutionGoal::tryToRun;
worker.waitForBuildSlot(shared_from_this());
}
@@ -1455,6 +1567,40 @@ void SubstitutionGoal::trace(const format & f)
//////////////////////////////////////////////////////////////////////
+/* A fake goal used to receive notification of success or failure of
+ other goals. */
+class PseudoGoal : public Goal
+{
+private:
+ bool success;
+
+public:
+ PseudoGoal(Worker & _worker) : Goal(_worker)
+ {
+ success = true;
+ }
+
+ void work()
+ {
+ abort();
+ }
+
+ void waiteeDone(bool success)
+ {
+ if (!success) this->success = false;
+ }
+
+ bool isOkay()
+ {
+ return success;
+ }
+};
+
+
+
+//////////////////////////////////////////////////////////////////////
+
+
static bool working = false;
@@ -1687,13 +1833,15 @@ Path normaliseStoreExpr(const Path & nePath)
startNest(nest, lvlDebug, format("normalising `%1%'") % nePath);
Worker worker;
- worker.addNormalisationGoal(nePath, GoalPtr());
+ shared_ptr<PseudoGoal> pseudo(new PseudoGoal(worker));
+ worker.addNormalisationGoal(nePath, pseudo);
worker.run();
+ if (!pseudo->isOkay())
+ throw Error(format("normalisation of store expression `%1%' failed") % nePath);
+
Path nfPath;
- if (!querySuccessor(nePath, nfPath))
- throw Error("there should be a successor");
-
+ if (!querySuccessor(nePath, nfPath)) abort();
return nfPath;
}
@@ -1703,8 +1851,12 @@ void realiseClosure(const Path & nePath)
startNest(nest, lvlDebug, format("realising closure `%1%'") % nePath);
Worker worker;
- worker.addRealisationGoal(nePath, GoalPtr());
+ shared_ptr<PseudoGoal> pseudo(new PseudoGoal(worker));
+ worker.addRealisationGoal(nePath, pseudo);
worker.run();
+
+ if (!pseudo->isOkay())
+ throw Error(format("realisation of closure `%1%' failed") % nePath);
}
@@ -1714,6 +1866,10 @@ void ensurePath(const Path & path)
if (isValidPath(path)) return;
Worker worker;
- worker.addSubstitutionGoal(path, GoalPtr());
+ shared_ptr<PseudoGoal> pseudo(new PseudoGoal(worker));
+ worker.addSubstitutionGoal(path, pseudo);
worker.run();
+
+ if (!pseudo->isOkay())
+ throw Error(format("path `%1%' does not exist and cannot be created") % path);
}