* Refactoring.

This commit is contained in:
Eelco Dolstra 2009-03-22 23:53:05 +00:00
parent d7b2d11255
commit 58969fa2bf

View file

@ -687,17 +687,6 @@ private:
/* Synchronously wait for a build hook to finish. */ /* Synchronously wait for a build hook to finish. */
void terminateBuildHook(bool kill = false); void terminateBuildHook(bool kill = false);
/* Acquires locks on the output paths and gathers information
about the build (e.g., the input closures). During this
process its possible that we find out that the build is
unnecessary, in which case we return prDone. It's also
possible that some other goal is already building/substituting
the output paths, in which case we return prRestart (go back to
the haveDerivation() state). Otherwise, prProceed is
returned. */
typedef enum {prProceed, prDone, prRestart} PrepareBuildReply;
PrepareBuildReply prepareBuild();
/* Start building a derivation. */ /* Start building a derivation. */
void startBuilder(); void startBuilder();
@ -834,19 +823,6 @@ void DerivationGoal::haveDerivation()
return; return;
} }
/* If this is a fixed-output derivation, it is possible that some
other goal is already building the output paths. (The case
where some other process is building it is handled through
normal locking mechanisms.) So if any output paths are already
being built, put this goal to sleep. */
foreach (PathSet::iterator, i, invalidOutputs)
if (pathIsLockedByMe(*i)) {
/* Wait until any goal finishes (hopefully the one that is
locking *i), then retry haveDerivation(). */
worker.waitForAnyGoal(shared_from_this());
return;
}
/* We are first going to try to create the invalid output paths /* We are first going to try to create the invalid output paths
through substitutes. If that doesn't work, we'll build through substitutes. If that doesn't work, we'll build
them. */ them. */
@ -948,52 +924,101 @@ void DerivationGoal::inputsRealised()
} }
PathSet outputPaths(const DerivationOutputs & outputs)
{
PathSet paths;
for (DerivationOutputs::const_iterator i = outputs.begin();
i != outputs.end(); ++i)
paths.insert(i->second.path);
return paths;
}
void DerivationGoal::tryToBuild() void DerivationGoal::tryToBuild()
{ {
trace("trying to build"); trace("trying to build");
/* Acquire locks on the output paths. After acquiring the locks, /* Check for the possibility that some other goal in this process
it might turn out that somebody else has performed the build has locked the output since we checked in haveDerivation().
already, and we're done. If not, we can proceed. */ (It can't happen between here and the lockPaths() call below
switch (prepareBuild()) { because we're not allowing multi-threading.) If so, put this
case prDone: goal to sleep until another goal finishes, then try again. */
amDone(ecSuccess); foreach (DerivationOutputs::iterator, i, drv.outputs)
return; if (pathIsLockedByMe(i->second.path)) {
case prRestart: debug(format("putting derivation `%1%' to sleep because `%2%' is locked by another goal")
% drvPath % i->second.path);
worker.waitForAnyGoal(shared_from_this()); worker.waitForAnyGoal(shared_from_this());
return; return;
case prProceed:
break;
}
try {
/* Is the build hook willing to accept this job? */
usingBuildHook = true;
switch (tryBuildHook()) {
case rpAccept:
/* Yes, it has started doing so. Wait until we get
EOF from the hook. */
state = &DerivationGoal::buildDone;
return;
case rpPostpone:
/* Not now; wait until at least one child finishes. */
worker.waitForChildTermination(shared_from_this());
outputLocks.unlock();
return;
case rpDecline:
/* We should do it ourselves. */
break;
} }
/* Obtain locks on all output paths. The locks are automatically
released when we exit this function or Nix crashes. */
/* !!! nonblock */
outputLocks.lockPaths(outputPaths(drv.outputs),
(format("waiting for lock on %1%") % showPaths(outputPaths(drv.outputs))).str());
usingBuildHook = false; /* Now check again whether the outputs are valid. This is because
another process may have started building in parallel. After
it has finished and released the locks, we can (and should)
reuse its results. (Strictly speaking the first check can be
omitted, but that would be less efficient.) Note that since we
now hold the locks on the output paths, no other process can
build this derivation, so no further checks are necessary. */
PathSet validPaths = checkPathValidity(true);
if (validPaths.size() == drv.outputs.size()) {
debug(format("skipping build of derivation `%1%', someone beat us to it")
% drvPath);
outputLocks.setDeletion(true);
amDone(ecSuccess);
return;
}
/* Make sure that we are allowed to start a build. */ if (validPaths.size() > 0)
if (!worker.canBuildMore()) { /* !!! fix this; try to delete valid paths */
worker.waitForBuildSlot(shared_from_this()); throw Error(
format("derivation `%1%' is blocked by its output paths")
% drvPath);
/* If any of the outputs already exist but are not valid, delete
them. */
foreach (DerivationOutputs::iterator, i, drv.outputs) {
Path path = i->second.path;
if (worker.store.isValidPath(path))
throw Error(format("obstructed build: path `%1%' exists") % path);
if (pathExists(path)) {
debug(format("removing unregistered path `%1%'") % path);
deletePathWrapped(path);
}
}
/* Is the build hook willing to accept this job? */
usingBuildHook = true;
switch (tryBuildHook()) {
case rpAccept:
/* Yes, it has started doing so. Wait until we get EOF
from the hook. */
state = &DerivationGoal::buildDone;
return;
case rpPostpone:
/* Not now; wait until at least one child finishes. */
worker.waitForChildTermination(shared_from_this());
outputLocks.unlock(); outputLocks.unlock();
return; return;
} case rpDecline:
/* We should do it ourselves. */
break;
}
usingBuildHook = false;
/* Make sure that we are allowed to start a build. */
if (!worker.canBuildMore()) {
worker.waitForBuildSlot(shared_from_this());
outputLocks.unlock();
return;
}
try {
/* Okay, we have to build. */ /* Okay, we have to build. */
startBuilder(); startBuilder();
@ -1003,12 +1028,8 @@ void DerivationGoal::tryToBuild()
outputLocks.unlock(); outputLocks.unlock();
buildUser.release(); buildUser.release();
if (printBuildTrace) if (printBuildTrace)
if (usingBuildHook) printMsg(lvlError, format("@ build-failed %1% %2% %3% %4%")
printMsg(lvlError, format("@ hook-failed %1% %2% %3% %4%") % drvPath % drv.outputs["out"].path % 0 % e.msg());
% drvPath % drv.outputs["out"].path % 0 % e.msg());
else
printMsg(lvlError, format("@ build-failed %1% %2% %3% %4%")
% drvPath % drv.outputs["out"].path % 0 % e.msg());
amDone(ecFailed); amDone(ecFailed);
return; return;
} }
@ -1179,16 +1200,6 @@ static void drain(int fd)
} }
PathSet outputPaths(const DerivationOutputs & outputs)
{
PathSet paths;
for (DerivationOutputs::const_iterator i = outputs.begin();
i != outputs.end(); ++i)
paths.insert(i->second.path);
return paths;
}
DerivationGoal::HookReply DerivationGoal::tryBuildHook() DerivationGoal::HookReply DerivationGoal::tryBuildHook()
{ {
if (!useBuildHook) return rpDecline; if (!useBuildHook) return rpDecline;
@ -1352,72 +1363,10 @@ void DerivationGoal::terminateBuildHook(bool kill)
} }
DerivationGoal::PrepareBuildReply DerivationGoal::prepareBuild()
{
/* Check for the possibility that some other goal in this process
has locked the output since we checked in haveDerivation().
(It can't happen between here and the lockPaths() call below
because we're not allowing multi-threading.) */
foreach (DerivationOutputs::iterator, i, drv.outputs)
if (pathIsLockedByMe(i->second.path)) {
debug(format("restarting derivation `%1%' because `%2%' is locked by another goal")
% drvPath % i->second.path);
return prRestart;
}
/* Obtain locks on all output paths. The locks are automatically
released when we exit this function or Nix crashes. */
/* !!! BUG: this could block, which is not allowed. */
/* !!! and once we make this non-blocking, we should carefully
consider the case where some but not all locks are required; we
should then release the acquired locks so that the other
processes and the pathIsLockedByMe() test don't get confused. */
outputLocks.lockPaths(outputPaths(drv.outputs),
(format("waiting for lock on %1%") % showPaths(outputPaths(drv.outputs))).str());
/* Now check again whether the outputs are valid. This is because
another process may have started building in parallel. After
it has finished and released the locks, we can (and should)
reuse its results. (Strictly speaking the first check can be
omitted, but that would be less efficient.) Note that since we
now hold the locks on the output paths, no other process can
build this derivation, so no further checks are necessary. */
PathSet validPaths = checkPathValidity(true);
if (validPaths.size() == drv.outputs.size()) {
debug(format("skipping build of derivation `%1%', someone beat us to it")
% drvPath);
outputLocks.setDeletion(true);
return prDone;
}
if (validPaths.size() > 0) {
/* !!! fix this; try to delete valid paths */
throw Error(
format("derivation `%1%' is blocked by its output paths")
% drvPath);
}
/* If any of the outputs already exist but are not registered,
delete them. */
foreach (DerivationOutputs::iterator, i, drv.outputs) {
Path path = i->second.path;
if (worker.store.isValidPath(path))
throw Error(format("obstructed build: path `%1%' exists") % path);
if (pathExists(path)) {
debug(format("removing unregistered path `%1%'") % path);
deletePathWrapped(path);
}
}
return prProceed;
}
void chmod(const Path & path, mode_t mode) void chmod(const Path & path, mode_t mode)
{ {
if (::chmod(path.c_str(), 01777) == -1) if (::chmod(path.c_str(), 01777) == -1)
throw SysError(format("setting permissions on `%1%'") % path); throw SysError(format("setting permissions on `%1%'") % path);
} }