daemon: Allow builds to be repeated.

This makes it easy to detect non-deterministic builds.

* nix/libstore/build.cc (DerivationGoal): Remove 'InodesSeen'; add
'curRound', 'nrRound', and 'prevInfos'.
(DerivationGoal::inputsRealised): Initialize 'nrRound'.
(NotDeterministic): New error type.
(DerivationGoal::buildDone): Check whether we need to repeat.
(DerivationGoal::startBuilder): Adjust message.
(DerivationGoal::registerOutputs): Check whether we get the same result.
* nix/libstore/globals.cc (Settings::get(const string & name, int def)):
New method.
* nix/libstore/globals.hh (Settings): Add it.
* nix/libstore/store-api.hh (ValidPathInfo): Add operator ==.
* nix/nix-daemon/nix-daemon.cc (performOp): Allow "build-repeat" for
"untrusted" users.

Co-authored-by: Ludovic Courtès <ludo@gnu.org>
This commit is contained in:
Eelco Dolstra 2015-12-08 22:50:18 +01:00 committed by Ludovic Courtès
parent 7fbee931a5
commit b23b4d394a
5 changed files with 71 additions and 14 deletions

View File

@ -785,10 +785,16 @@ private:
temporary paths. */ temporary paths. */
PathSet redirectedBadOutputs; PathSet redirectedBadOutputs;
/* Set of inodes seen during calls to canonicalisePathMetaData() /* The current round, if we're building multiple times. */
for this build's outputs. This needs to be shared between unsigned int curRound = 1;
outputs to allow hard links between outputs. */
InodesSeen inodesSeen; unsigned int nrRounds;
/* Path registration info from the previous round, if we're
building multiple times. Since this contains the hash, it
allows us to compare whether two rounds produced the same
result. */
ValidPathInfos prevInfos;
public: public:
DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode = bmNormal); DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode = bmNormal);
@ -1194,8 +1200,12 @@ void DerivationGoal::inputsRealised()
/* Is this a fixed-output derivation? */ /* Is this a fixed-output derivation? */
fixedOutput = true; fixedOutput = true;
foreach (DerivationOutputs::iterator, i, drv.outputs) for (auto & i : drv.outputs)
if (i->second.hash == "") fixedOutput = false; if (i.second.hash == "") fixedOutput = false;
/* Don't repeat fixed-output derivations since they're already
verified by their output hash.*/
nrRounds = fixedOutput ? 1 : settings.get("build-repeat", 0) + 1;
/* Okay, try to build. Note that here we don't wait for a build /* 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 slot to become available, since we don't need one if there is a
@ -1371,6 +1381,9 @@ void replaceValidPath(const Path & storePath, const Path tmpPath)
} }
MakeError(NotDeterministic, BuildError)
void DerivationGoal::buildDone() void DerivationGoal::buildDone()
{ {
trace("build done"); trace("build done");
@ -1470,6 +1483,15 @@ void DerivationGoal::buildDone()
deleteTmpDir(true); deleteTmpDir(true);
/* Repeat the build if necessary. */
if (curRound++ < nrRounds) {
outputLocks.unlock();
buildUser.release();
state = &DerivationGoal::tryToBuild;
worker.wakeUp(shared_from_this());
return;
}
/* It is now safe to delete the lock files, since all future /* It is now safe to delete the lock files, since all future
lockers will see that the output paths are valid; they will lockers will see that the output paths are valid; they will
not create new lock files with the same names as the old not create new lock files with the same names as the old
@ -1623,10 +1645,13 @@ int childEntry(void * arg)
void DerivationGoal::startBuilder() void DerivationGoal::startBuilder()
{ {
startNest(nest, lvlInfo, format( auto f = format(
buildMode == bmRepair ? "repairing path(s) %1%" : buildMode == bmRepair ? "repairing path(s) %1%" :
buildMode == bmCheck ? "checking path(s) %1%" : buildMode == bmCheck ? "checking path(s) %1%" :
"building path(s) %1%") % showPaths(missingPaths)); nrRounds > 1 ? "building path(s) %1% (round %2%/%3%)" :
"building path(s) %1%");
f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
startNest(nest, lvlInfo, f % showPaths(missingPaths) % curRound % nrRounds);
/* Right platform? */ /* Right platform? */
if (!canBuildLocally(drv.platform)) { if (!canBuildLocally(drv.platform)) {
@ -1638,6 +1663,7 @@ void DerivationGoal::startBuilder()
} }
/* Construct the environment passed to the builder. */ /* Construct the environment passed to the builder. */
env.clear();
/* Most shells initialise PATH to some default (/bin:/usr/bin:...) when /* Most shells initialise PATH to some default (/bin:/usr/bin:...) when
PATH is not set. We don't want this, so we fill it in with some dummy PATH is not set. We don't want this, so we fill it in with some dummy
@ -2267,6 +2293,11 @@ void DerivationGoal::registerOutputs()
ValidPathInfos infos; ValidPathInfos infos;
/* Set of inodes seen during calls to canonicalisePathMetaData()
for this build's outputs. This needs to be shared between
outputs to allow hard links between outputs. */
InodesSeen inodesSeen;
/* Check whether the output paths were created, and grep each /* Check whether the output paths were created, and grep each
output path to determine what other paths it references. Also make all output path to determine what other paths it references. Also make all
output paths read-only. */ output paths read-only. */
@ -2438,6 +2469,16 @@ void DerivationGoal::registerOutputs()
if (buildMode == bmCheck) return; if (buildMode == bmCheck) return;
if (curRound > 1 && prevInfos != infos)
throw NotDeterministic(
format("result of %1% differs from previous round; rejecting as non-deterministic")
% drvPath);
if (curRound < nrRounds) {
prevInfos = infos;
return;
}
/* Register each output path as valid, and register the sets of /* Register each output path as valid, and register the sets of
paths referenced by each of them. If there are cycles in the paths referenced by each of them. If there are cycles in the
outputs, this will fail. */ outputs, this will fail. */

View File

@ -137,6 +137,13 @@ bool Settings::get(const string & name, bool def)
return res; return res;
} }
int Settings::get(const string & name, int def)
{
int res = def;
_get(res, name);
return res;
}
void Settings::update() void Settings::update()
{ {

View File

@ -27,6 +27,8 @@ struct Settings {
bool get(const string & name, bool def); bool get(const string & name, bool def);
int get(const string & name, int def);
void update(); void update();
string pack(); string pack();

View File

@ -88,10 +88,17 @@ struct ValidPathInfo
Path deriver; Path deriver;
Hash hash; Hash hash;
PathSet references; PathSet references;
time_t registrationTime; time_t registrationTime = 0;
unsigned long long narSize; // 0 = unknown unsigned long long narSize = 0; // 0 = unknown
unsigned long long id; // internal use only unsigned long long id; // internal use only
ValidPathInfo() : registrationTime(0), narSize(0) { }
bool operator == (const ValidPathInfo & i) const
{
return
path == i.path
&& hash == i.hash
&& references == i.references;
}
}; };
typedef list<ValidPathInfo> ValidPathInfos; typedef list<ValidPathInfo> ValidPathInfos;

View File

@ -565,7 +565,7 @@ static void performOp(bool trusted, unsigned int clientVersion,
for (unsigned int i = 0; i < n; i++) { for (unsigned int i = 0; i < n; i++) {
string name = readString(from); string name = readString(from);
string value = readString(from); string value = readString(from);
if (name == "build-timeout" || name == "use-ssh-substituter") if (name == "build-timeout" || name == "build-repeat" || name == "use-ssh-substituter")
settings.set(name, value); settings.set(name, value);
else else
settings.set(trusted ? name : "untrusted-" + name, value); settings.set(trusted ? name : "untrusted-" + name, value);