diff options
Diffstat (limited to 'src/libstore')
-rw-r--r-- | src/libstore/build.cc | 125 | ||||
-rw-r--r-- | src/libstore/derivations.cc | 2 | ||||
-rw-r--r-- | src/libstore/gc.cc | 5 | ||||
-rw-r--r-- | src/libstore/globals.hh | 5 | ||||
-rw-r--r-- | src/libstore/nar-accessor.cc | 3 | ||||
-rw-r--r-- | src/libstore/pathlocks.cc | 87 | ||||
-rw-r--r-- | src/libstore/s3-binary-cache-store.cc | 29 | ||||
-rw-r--r-- | src/libstore/store-api.hh | 15 |
8 files changed, 188 insertions, 83 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc index ee266b7d3704..ca56d3ad925b 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -17,9 +17,9 @@ #include <sstream> #include <thread> #include <future> +#include <chrono> #include <limits.h> -#include <time.h> #include <sys/time.h> #include <sys/wait.h> #include <sys/types.h> @@ -187,6 +187,9 @@ bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) { } +typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point; + + /* A mapping used to remember for each child process to what goal it belongs, and file descriptors for receiving log data and output path creation commands. */ @@ -197,8 +200,8 @@ struct Child set<int> fds; bool respectTimeouts; bool inBuildSlot; - time_t lastOutput; /* time we last got output on stdout/stderr */ - time_t timeStarted; + steady_time_point lastOutput; /* time we last got output on stdout/stderr */ + steady_time_point timeStarted; }; @@ -238,7 +241,7 @@ private: WeakGoals waitingForAWhile; /* Last time the goals in `waitingForAWhile' where woken up. */ - time_t lastWokenUp; + steady_time_point lastWokenUp; /* Cache for pathContentsGood(). */ std::map<Path, bool> pathContentsGoodCache; @@ -1269,6 +1272,8 @@ void DerivationGoal::inputsRealised() build hook. */ state = &DerivationGoal::tryToBuild; worker.wakeUp(shared_from_this()); + + result = BuildResult(); } @@ -1342,6 +1347,7 @@ void DerivationGoal::tryToBuild() case rpAccept: /* Yes, it has started doing so. Wait until we get EOF from the hook. */ + result.startTime = time(0); // inexact state = &DerivationGoal::buildDone; return; case rpPostpone: @@ -1418,6 +1424,9 @@ void DerivationGoal::buildDone() debug(format("builder process for ‘%1%’ finished") % drvPath); + result.timesBuilt++; + result.stopTime = time(0); + /* So the child is gone now. */ worker.childTerminated(this); @@ -2101,6 +2110,8 @@ void DerivationGoal::startBuilder() /* Create a pipe to get the output of the builder. */ builderOut.create(); + result.startTime = time(0); + /* Fork a child to build the package. */ #if __linux__ if (useChroot) { @@ -2154,7 +2165,8 @@ void DerivationGoal::startBuilder() namespace, we can't drop additional groups; they will be mapped to nogroup in the child namespace. There does not seem to be a workaround for this. (But who can tell - from reading user_namespaces(7)?)*/ + from reading user_namespaces(7)?) + See also https://lwn.net/Articles/621612/. */ if (getuid() == 0 && setgroups(0, 0) == -1) throw SysError("setgroups failed"); @@ -2324,6 +2336,7 @@ void DerivationGoal::runChild() ss.push_back("/etc/nsswitch.conf"); ss.push_back("/etc/services"); ss.push_back("/etc/hosts"); + ss.push_back("/var/run/nscd/socket"); } for (auto & i : ss) dirsInChroot[i] = i; @@ -2672,7 +2685,9 @@ void DerivationGoal::registerOutputs() outputs to allow hard links between outputs. */ InodesSeen inodesSeen; - Path checkSuffix = "-check"; + Path checkSuffix = ".check"; + bool runDiffHook = settings.get("run-diff-hook", false); + bool keepPreviousRound = settings.keepFailed || runDiffHook; /* Check whether the output paths were created, and grep each output path to determine what other paths it references. Also make all @@ -2902,30 +2917,42 @@ void DerivationGoal::registerOutputs() assert(prevInfos.size() == infos.size()); for (auto i = prevInfos.begin(), j = infos.begin(); i != prevInfos.end(); ++i, ++j) if (!(*i == *j)) { + result.isNonDeterministic = true; Path prev = i->path + checkSuffix; - if (pathExists(prev)) - throw NotDeterministic( - format("output ‘%1%’ of ‘%2%’ differs from ‘%3%’ from previous round") - % i->path % drvPath % prev); - else - throw NotDeterministic( - format("output ‘%1%’ of ‘%2%’ differs from previous round") - % i->path % drvPath); + bool prevExists = keepPreviousRound && pathExists(prev); + auto msg = prevExists + ? fmt("output ‘%1%’ of ‘%2%’ differs from ‘%3%’ from previous round", i->path, drvPath, prev) + : fmt("output ‘%1%’ of ‘%2%’ differs from previous round", i->path, drvPath); + + auto diffHook = settings.get("diff-hook", std::string("")); + if (prevExists && diffHook != "" && runDiffHook) { + try { + auto diff = runProgram(diffHook, true, {prev, i->path}); + if (diff != "") + printError(chomp(diff)); + } catch (Error & error) { + printError("diff hook execution failed: %s", error.what()); + } + } + + if (settings.get("enforce-determinism", true)) + throw NotDeterministic(msg); + + printError(msg); + curRound = nrRounds; // we know enough, bail out early } - abort(); // shouldn't happen } - if (settings.keepFailed) { + /* If this is the first round of several, then move the output out + of the way. */ + if (nrRounds > 1 && curRound == 1 && curRound < nrRounds && keepPreviousRound) { for (auto & i : drv->outputs) { Path prev = i.second.path + checkSuffix; deletePath(prev); - if (curRound < nrRounds) { - Path dst = i.second.path + checkSuffix; - if (rename(i.second.path.c_str(), dst.c_str())) - throw SysError(format("renaming ‘%1%’ to ‘%2%’") % i.second.path % dst); - } + Path dst = i.second.path + checkSuffix; + if (rename(i.second.path.c_str(), dst.c_str())) + throw SysError(format("renaming ‘%1%’ to ‘%2%’") % i.second.path % dst); } - } if (curRound < nrRounds) { @@ -2933,6 +2960,15 @@ void DerivationGoal::registerOutputs() return; } + /* Remove the .check directories if we're done. FIXME: keep them + if the result was not determistic? */ + if (curRound == nrRounds) { + for (auto & i : drv->outputs) { + Path prev = i.second.path + checkSuffix; + deletePath(prev); + } + } + /* Register each output path as valid, and register the sets of paths referenced by each of them. If there are cycles in the outputs, this will fail. */ @@ -3043,7 +3079,8 @@ void DerivationGoal::handleEOF(int fd) void DerivationGoal::flushLine() { - if (settings.verboseBuild) + if (settings.verboseBuild && + (settings.printRepeatedBuilds || curRound == 1)) printError(filterANSIEscapes(currentLogLine, true)); else { logTail.push_back(currentLogLine); @@ -3385,7 +3422,7 @@ Worker::Worker(LocalStore & store) if (working) abort(); working = true; nrLocalBuilds = 0; - lastWokenUp = 0; + lastWokenUp = steady_time_point::min(); permanentFailure = false; timedOut = false; } @@ -3494,7 +3531,7 @@ void Worker::childStarted(GoalPtr goal, const set<int> & fds, child.goal = goal; child.goal2 = goal.get(); child.fds = fds; - child.timeStarted = child.lastOutput = time(0); + child.timeStarted = child.lastOutput = steady_time_point::clock::now(); child.inBuildSlot = inBuildSlot; child.respectTimeouts = respectTimeouts; children.emplace_back(child); @@ -3613,35 +3650,38 @@ void Worker::waitForInput() bool useTimeout = false; struct timeval timeout; timeout.tv_usec = 0; - time_t before = time(0); + auto before = steady_time_point::clock::now(); /* If we're monitoring for silence on stdout/stderr, or if there is a build timeout, then wait for input until the first deadline for any child. */ - assert(sizeof(time_t) >= sizeof(long)); - time_t nearest = LONG_MAX; // nearest deadline + auto nearest = steady_time_point::max(); // nearest deadline for (auto & i : children) { if (!i.respectTimeouts) continue; if (settings.maxSilentTime != 0) - nearest = std::min(nearest, i.lastOutput + settings.maxSilentTime); + nearest = std::min(nearest, i.lastOutput + std::chrono::seconds(settings.maxSilentTime)); if (settings.buildTimeout != 0) - nearest = std::min(nearest, i.timeStarted + settings.buildTimeout); + nearest = std::min(nearest, i.timeStarted + std::chrono::seconds(settings.buildTimeout)); } - if (nearest != LONG_MAX) { - timeout.tv_sec = std::max((time_t) 1, nearest - before); + if (nearest != steady_time_point::max()) { + timeout.tv_sec = std::max(1L, (long) std::chrono::duration_cast<std::chrono::seconds>(nearest - before).count()); useTimeout = true; - printMsg(lvlVomit, format("sleeping %1% seconds") % timeout.tv_sec); } /* If we are polling goals that are waiting for a lock, then wake up after a few seconds at most. */ if (!waitingForAWhile.empty()) { useTimeout = true; - if (lastWokenUp == 0) + if (lastWokenUp == steady_time_point::min()) printError("waiting for locks or build slots..."); - if (lastWokenUp == 0 || lastWokenUp > before) lastWokenUp = before; - timeout.tv_sec = std::max((time_t) 1, (time_t) (lastWokenUp + settings.pollInterval - before)); - } else lastWokenUp = 0; + if (lastWokenUp == steady_time_point::min() || lastWokenUp > before) lastWokenUp = before; + timeout.tv_sec = std::max(1L, + (long) std::chrono::duration_cast<std::chrono::seconds>( + lastWokenUp + std::chrono::seconds(settings.pollInterval) - before).count()); + } else lastWokenUp = steady_time_point::min(); + + if (useTimeout) + vomit("sleeping %d seconds", timeout.tv_sec); /* Use select() to wait for the input side of any logger pipe to become `available'. Note that `available' (i.e., non-blocking) @@ -3661,7 +3701,7 @@ void Worker::waitForInput() throw SysError("waiting for input"); } - time_t after = time(0); + auto after = steady_time_point::clock::now(); /* Process all available file descriptors. */ decltype(children)::iterator i; @@ -3699,7 +3739,7 @@ void Worker::waitForInput() if (goal->getExitCode() == Goal::ecBusy && settings.maxSilentTime != 0 && j->respectTimeouts && - after - j->lastOutput >= (time_t) settings.maxSilentTime) + after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime)) { printError( format("%1% timed out after %2% seconds of silence") @@ -3710,7 +3750,7 @@ void Worker::waitForInput() else if (goal->getExitCode() == Goal::ecBusy && settings.buildTimeout != 0 && j->respectTimeouts && - after - j->timeStarted >= (time_t) settings.buildTimeout) + after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout)) { printError( format("%1% timed out after %2% seconds") @@ -3719,7 +3759,7 @@ void Worker::waitForInput() } } - if (!waitingForAWhile.empty() && lastWokenUp + (time_t) settings.pollInterval <= after) { + if (!waitingForAWhile.empty() && lastWokenUp + std::chrono::seconds(settings.pollInterval) <= after) { lastWokenUp = after; for (auto & i : waitingForAWhile) { GoalPtr goal = i.lock(); @@ -3781,12 +3821,13 @@ void LocalStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode) worker.run(goals); PathSet failed; - for (auto & i : goals) + for (auto & i : goals) { if (i->getExitCode() != Goal::ecSuccess) { DerivationGoal * i2 = dynamic_cast<DerivationGoal *>(i.get()); if (i2) failed.insert(i2->getDrvPath()); else failed.insert(dynamic_cast<SubstitutionGoal *>(i.get())->getStorePath()); } + } if (!failed.empty()) throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed)); diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 5562d46892a8..d934bda38225 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -155,7 +155,7 @@ static StringSet parseStrings(std::istream & str, bool arePaths) static Derivation parseDerivation(const string & s) { Derivation drv; - istringstream_nocopy str(s); + std::istringstream str(s); expect(str, "Derive(["); /* Parse the list of outputs. */ diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index ae03604faf98..f8c4a07238c7 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -621,6 +621,11 @@ void LocalStore::tryToDelete(GCState & state, const Path & path) /* Don't delete .chroot directories for derivations that are currently being built. */ if (isActiveTempFile(state, path, ".chroot")) return; + + /* Don't delete .check directories for derivations that are + currently being built, because we may need to run + diff-hook. */ + if (isActiveTempFile(state, path, ".check")) return; } PathSet visited; diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 3194193bc842..a423b4e5c0f4 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -149,6 +149,11 @@ struct Settings { before being killed (0 means no limit). */ unsigned long maxLogSize; + /* When build-repeat > 0 and verboseBuild == true, whether to + print repeated builds (i.e. builds other than the first one) to + stderr. Hack to prevent Hydra logs from being polluted. */ + bool printRepeatedBuilds = true; + /* How often (in seconds) to poll for locks. */ unsigned int pollInterval; diff --git a/src/libstore/nar-accessor.cc b/src/libstore/nar-accessor.cc index ded19c05d2cd..4cb5de7449ea 100644 --- a/src/libstore/nar-accessor.cc +++ b/src/libstore/nar-accessor.cc @@ -52,8 +52,9 @@ struct NarIndexer : ParseSink, StringSource void preallocateContents(unsigned long long size) override { currentStart = string(s, pos, 16); + assert(size <= std::numeric_limits<size_t>::max()); members.emplace(currentPath, - NarMember{FSAccessor::Type::tRegular, isExec, pos, size}); + NarMember{FSAccessor::Type::tRegular, isExec, pos, (size_t) size}); } void receiveContents(unsigned char * data, unsigned int len) override diff --git a/src/libstore/pathlocks.cc b/src/libstore/pathlocks.cc index 8788ee1649fb..fecd636877af 100644 --- a/src/libstore/pathlocks.cc +++ b/src/libstore/pathlocks.cc @@ -1,5 +1,6 @@ #include "pathlocks.hh" #include "util.hh" +#include "sync.hh" #include <cerrno> #include <cstdlib> @@ -72,7 +73,7 @@ bool lockFile(int fd, LockType lockType, bool wait) close a descriptor, the previous lock will be closed as well. And there is no way to query whether we already have a lock (F_GETLK only works on locks held by other processes). */ -static StringSet lockedPaths; /* !!! not thread-safe */ +static Sync<StringSet> lockedPaths_; PathLocks::PathLocks() @@ -108,49 +109,60 @@ bool PathLocks::lockPaths(const PathSet & _paths, debug(format("locking path ‘%1%’") % path); - if (lockedPaths.find(lockPath) != lockedPaths.end()) - throw Error("deadlock: trying to re-acquire self-held lock"); + { + auto lockedPaths(lockedPaths_.lock()); + if (lockedPaths->count(lockPath)) + throw Error("deadlock: trying to re-acquire self-held lock ‘%s’", lockPath); + lockedPaths->insert(lockPath); + } + + try { - AutoCloseFD fd; + AutoCloseFD fd; - while (1) { + while (1) { - /* Open/create the lock file. */ - fd = openLockFile(lockPath, true); + /* Open/create the lock file. */ + fd = openLockFile(lockPath, true); - /* Acquire an exclusive lock. */ - if (!lockFile(fd.get(), ltWrite, false)) { - if (wait) { - if (waitMsg != "") printError(waitMsg); - lockFile(fd.get(), ltWrite, true); - } else { - /* Failed to lock this path; release all other - locks. */ - unlock(); - return false; + /* Acquire an exclusive lock. */ + if (!lockFile(fd.get(), ltWrite, false)) { + if (wait) { + if (waitMsg != "") printError(waitMsg); + lockFile(fd.get(), ltWrite, true); + } else { + /* Failed to lock this path; release all other + locks. */ + unlock(); + return false; + } } + + debug(format("lock acquired on ‘%1%’") % lockPath); + + /* Check that the lock file hasn't become stale (i.e., + hasn't been unlinked). */ + struct stat st; + if (fstat(fd.get(), &st) == -1) + throw SysError(format("statting lock file ‘%1%’") % lockPath); + if (st.st_size != 0) + /* This lock file has been unlinked, so we're holding + a lock on a deleted file. This means that other + processes may create and acquire a lock on + `lockPath', and proceed. So we must retry. */ + debug(format("open lock file ‘%1%’ has become stale") % lockPath); + else + break; } - debug(format("lock acquired on ‘%1%’") % lockPath); - - /* Check that the lock file hasn't become stale (i.e., - hasn't been unlinked). */ - struct stat st; - if (fstat(fd.get(), &st) == -1) - throw SysError(format("statting lock file ‘%1%’") % lockPath); - if (st.st_size != 0) - /* This lock file has been unlinked, so we're holding - a lock on a deleted file. This means that other - processes may create and acquire a lock on - `lockPath', and proceed. So we must retry. */ - debug(format("open lock file ‘%1%’ has become stale") % lockPath); - else - break; + /* Use borrow so that the descriptor isn't closed. */ + fds.push_back(FDPair(fd.release(), lockPath)); + + } catch (...) { + lockedPaths_.lock()->erase(lockPath); + throw; } - /* Use borrow so that the descriptor isn't closed. */ - fds.push_back(FDPair(fd.release(), lockPath)); - lockedPaths.insert(lockPath); } return true; @@ -172,7 +184,8 @@ void PathLocks::unlock() for (auto & i : fds) { if (deletePaths) deleteLockFile(i.second, i.first); - lockedPaths.erase(i.second); + lockedPaths_.lock()->erase(i.second); + if (close(i.first) == -1) printError( format("error (ignored): cannot close lock file on ‘%1%’") % i.second); @@ -193,7 +206,7 @@ void PathLocks::setDeletion(bool deletePaths) bool pathIsLockedByMe(const Path & path) { Path lockPath = path + ".lock"; - return lockedPaths.find(lockPath) != lockedPaths.end(); + return lockedPaths_.lock()->count(lockPath); } diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc index 1bc8576a8aef..ccb71f1eefe5 100644 --- a/src/libstore/s3-binary-cache-store.cc +++ b/src/libstore/s3-binary-cache-store.cc @@ -1,23 +1,34 @@ #include "config.h" #if ENABLE_S3 +#if __linux__ #include "s3-binary-cache-store.hh" #include "nar-info.hh" #include "nar-info-disk-cache.hh" #include "globals.hh" +#include <aws/core/Aws.h> #include <aws/core/client/ClientConfiguration.h> #include <aws/s3/S3Client.h> #include <aws/s3/model/CreateBucketRequest.h> #include <aws/s3/model/GetBucketLocationRequest.h> #include <aws/s3/model/GetObjectRequest.h> #include <aws/s3/model/HeadObjectRequest.h> -#include <aws/s3/model/PutObjectRequest.h> #include <aws/s3/model/ListObjectsRequest.h> +#include <aws/s3/model/PutObjectRequest.h> namespace nix { +struct istringstream_nocopy : public std::stringstream +{ + istringstream_nocopy(const std::string & s) + { + rdbuf()->pubsetbuf( + (char *) s.data(), s.size()); + } +}; + struct S3Error : public Error { Aws::S3::S3Errors err; @@ -37,6 +48,20 @@ R && checkAws(const FormatOrString & fs, Aws::Utils::Outcome<R, E> && outcome) return outcome.GetResultWithOwnership(); } +static void initAWS() +{ + static std::once_flag flag; + std::call_once(flag, []() { + Aws::SDKOptions options; + + /* We install our own OpenSSL locking function (see + shared.cc), so don't let aws-sdk-cpp override it. */ + options.cryptoOptions.initAndCleanupOpenSSL = false; + + Aws::InitAPI(options); + }); +} + struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore { std::string bucketName; @@ -63,6 +88,7 @@ struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore ref<Aws::Client::ClientConfiguration> makeConfig() { + initAWS(); auto res = make_ref<Aws::Client::ClientConfiguration>(); res->region = Aws::Region::US_EAST_1; // FIXME: make configurable res->requestTimeoutMs = 600 * 1000; @@ -260,3 +286,4 @@ static RegisterStoreImplementation regStore([]( } #endif +#endif diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index 2ea74d90e78e..789526cc2b70 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -208,7 +208,20 @@ struct BuildResult NotDeterministic, } status = MiscFailure; std::string errorMsg; - //time_t startTime = 0, stopTime = 0; + + /* How many times this build was performed. */ + unsigned int timesBuilt = 0; + + /* If timesBuilt > 1, whether some builds did not produce the same + result. (Note that 'isNonDeterministic = false' does not mean + the build is deterministic, just that we don't have evidence of + non-determinism.) */ + bool isNonDeterministic = false; + + /* The start/stop times of the build (or one of the rounds, if it + was repeated). */ + time_t startTime = 0, stopTime = 0; + bool success() { return status == Built || status == Substituted || status == AlreadyValid; } |