1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-15 15:02:42 +01:00

Merge pull request #13860 from obsidiansystems/derivation-building-resources-code-cleanup

Derivation building resources code cleanup
This commit is contained in:
Jörg Thalheim 2025-09-01 20:22:30 +02:00 committed by GitHub
commit de7f137f31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 127 additions and 136 deletions

View file

@ -54,24 +54,9 @@ DerivationBuildingGoal::~DerivationBuildingGoal()
{ {
/* Careful: we should never ever throw an exception from a /* Careful: we should never ever throw an exception from a
destructor. */ destructor. */
try {
killChild();
} catch (...) {
ignoreExceptionInDestructor();
}
#ifndef _WIN32 // TODO enable `DerivationBuilder` on Windows #ifndef _WIN32 // TODO enable `DerivationBuilder` on Windows
if (builder) { if (builder)
try { builder.reset();
builder->stopDaemon();
} catch (...) {
ignoreExceptionInDestructor();
}
try {
builder->deleteTmpDir(false);
} catch (...) {
ignoreExceptionInDestructor();
}
}
#endif #endif
try { try {
closeLogFile(); closeLogFile();
@ -95,22 +80,8 @@ void DerivationBuildingGoal::killChild()
hook.reset(); hook.reset();
#endif #endif
#ifndef _WIN32 // TODO enable `DerivationBuilder` on Windows #ifndef _WIN32 // TODO enable `DerivationBuilder` on Windows
if (builder && builder->pid != -1) { if (builder && builder->killChild())
worker.childTerminated(this); worker.childTerminated(this);
// FIXME: move this into DerivationBuilder.
/* If we're using a build user, then there is a tricky race
condition: if we kill the build user before the child has
done its setuid() to the build user uid, then it won't be
killed, and we'll potentially lock up in pid.wait(). So
also send a conventional kill to the child. */
::kill(-builder->pid, SIGKILL); /* ignore the result */
builder->killSandbox(true);
builder->pid.wait();
}
#endif #endif
} }
@ -653,11 +624,6 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
goal.worker.childTerminated(&goal); goal.worker.childTerminated(&goal);
} }
void markContentsGood(const StorePath & path) override
{
goal.worker.markContentsGood(path);
}
Path openLogFile() override Path openLogFile() override
{ {
return goal.openLogFile(); return goal.openLogFile();
@ -756,21 +722,22 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
trace("build done"); trace("build done");
auto res = builder->unprepareBuild(); SingleDrvOutputs builtOutputs;
// N.B. cannot use `std::visit` with co-routine return try {
if (auto * ste = std::get_if<0>(&res)) { builtOutputs = builder->unprepareBuild();
} catch (BuildError & e) {
outputLocks.unlock(); outputLocks.unlock();
// Allow selecting a subset of enum values // Allow selecting a subset of enum values
# pragma GCC diagnostic push # pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wswitch-enum" # pragma GCC diagnostic ignored "-Wswitch-enum"
switch (ste->status) { switch (e.status) {
case BuildResult::HashMismatch: case BuildResult::HashMismatch:
worker.hashMismatch = true; worker.hashMismatch = true;
/* See header, the protocols don't know about `HashMismatch` /* See header, the protocols don't know about `HashMismatch`
yet, so change it to `OutputRejected`, which they expect yet, so change it to `OutputRejected`, which they expect
for this case (hash mismatch is a type of output for this case (hash mismatch is a type of output
rejection). */ rejection). */
ste->status = BuildResult::OutputRejected; e.status = BuildResult::OutputRejected;
break; break;
case BuildResult::NotDeterministic: case BuildResult::NotDeterministic:
worker.checkMismatch = true; worker.checkMismatch = true;
@ -780,11 +747,15 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
break; break;
} }
# pragma GCC diagnostic pop # pragma GCC diagnostic pop
co_return doneFailure(std::move(*ste)); co_return doneFailure(std::move(e));
} else if (auto * builtOutputs = std::get_if<1>(&res)) { }
{
StorePathSet outputPaths; StorePathSet outputPaths;
for (auto & [_, output] : *builtOutputs) for (auto & [_, output] : builtOutputs) {
// for sake of `bmRepair`
worker.markContentsGood(output.outPath);
outputPaths.insert(output.outPath); outputPaths.insert(output.outPath);
}
runPostBuildHook(worker.store, *logger, drvPath, outputPaths); runPostBuildHook(worker.store, *logger, drvPath, outputPaths);
/* It is now safe to delete the lock files, since all future /* It is now safe to delete the lock files, since all future
@ -793,9 +764,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
(unlinked) lock files. */ (unlinked) lock files. */
outputLocks.setDeletion(true); outputLocks.setDeletion(true);
outputLocks.unlock(); outputLocks.unlock();
co_return doneSuccess(BuildResult::Built, std::move(*builtOutputs)); co_return doneSuccess(BuildResult::Built, std::move(builtOutputs));
} else {
unreachable();
} }
#endif #endif
} }

View file

@ -107,8 +107,6 @@ struct DerivationBuilderCallbacks
* @todo this should be reworked * @todo this should be reworked
*/ */
virtual void childTerminated() = 0; virtual void childTerminated() = 0;
virtual void markContentsGood(const StorePath & path) = 0;
}; };
/** /**
@ -124,11 +122,6 @@ struct DerivationBuilderCallbacks
*/ */
struct DerivationBuilder : RestrictionContext struct DerivationBuilder : RestrictionContext
{ {
/**
* The process ID of the builder.
*/
Pid pid;
DerivationBuilder() = default; DerivationBuilder() = default;
virtual ~DerivationBuilder() = default; virtual ~DerivationBuilder() = default;
@ -161,25 +154,18 @@ struct DerivationBuilder : RestrictionContext
* processing. A status code and exception are returned, providing * processing. A status code and exception are returned, providing
* more information. The second case indicates success, and * more information. The second case indicates success, and
* realisations for each output of the derivation are returned. * realisations for each output of the derivation are returned.
*
* @throws BuildError
*/ */
virtual std::variant<BuildError, SingleDrvOutputs> unprepareBuild() = 0; virtual SingleDrvOutputs unprepareBuild() = 0;
/** /**
* Stop the in-process nix daemon thread. * Forcibly kill the child process, if any.
* @see startDaemon *
* @returns whether the child was still alive and needed to be
* killed.
*/ */
virtual void stopDaemon() = 0; virtual bool killChild() = 0;
/**
* Delete the temporary directory, if we have one.
*/
virtual void deleteTmpDir(bool force) = 0;
/**
* Kill any processes running under the build user UID or in the
* cgroup of the build.
*/
virtual void killSandbox(bool getStats) = 0;
}; };
#ifndef _WIN32 // TODO enable `DerivationBuilder` on Windows #ifndef _WIN32 // TODO enable `DerivationBuilder` on Windows

View file

@ -22,13 +22,6 @@ struct ChrootDerivationBuilder : virtual DerivationBuilderImpl
PathsInChroot pathsInChroot; PathsInChroot pathsInChroot;
void deleteTmpDir(bool force) override
{
autoDelChroot.reset(); /* this runs the destructor */
DerivationBuilderImpl::deleteTmpDir(force);
}
bool needsHashRewrite() override bool needsHashRewrite() override
{ {
return false; return false;
@ -166,13 +159,13 @@ struct ChrootDerivationBuilder : virtual DerivationBuilderImpl
return !needsHashRewrite() ? chrootRootDir + p : store.toRealPath(p); return !needsHashRewrite() ? chrootRootDir + p : store.toRealPath(p);
} }
void cleanupBuild() override void cleanupBuild(bool force) override
{ {
DerivationBuilderImpl::cleanupBuild(); DerivationBuilderImpl::cleanupBuild(force);
/* Move paths out of the chroot for easier debugging of /* Move paths out of the chroot for easier debugging of
build failures. */ build failures. */
if (buildMode == bmNormal) if (!force && buildMode == bmNormal)
for (auto & [_, status] : initialOutputs) { for (auto & [_, status] : initialOutputs) {
if (!status.known) if (!status.known)
continue; continue;
@ -182,6 +175,8 @@ struct ChrootDerivationBuilder : virtual DerivationBuilderImpl
if (pathExists(chrootRootDir + p)) if (pathExists(chrootRootDir + p))
std::filesystem::rename((chrootRootDir + p), p); std::filesystem::rename((chrootRootDir + p), p);
} }
autoDelChroot.reset(); /* this runs the destructor */
} }
std::pair<Path, Path> addDependencyPrep(const StorePath & path) std::pair<Path, Path> addDependencyPrep(const StorePath & path)

View file

@ -71,6 +71,11 @@ class DerivationBuilderImpl : public DerivationBuilder, public DerivationBuilder
{ {
protected: protected:
/**
* The process ID of the builder.
*/
Pid pid;
LocalStore & store; LocalStore & store;
std::unique_ptr<DerivationBuilderCallbacks> miscMethods; std::unique_ptr<DerivationBuilderCallbacks> miscMethods;
@ -86,6 +91,27 @@ public:
{ {
} }
~DerivationBuilderImpl()
{
/* Careful: we should never ever throw an exception from a
destructor. */
try {
killChild();
} catch (...) {
ignoreExceptionInDestructor();
}
try {
stopDaemon();
} catch (...) {
ignoreExceptionInDestructor();
}
try {
cleanupBuild(false);
} catch (...) {
ignoreExceptionInDestructor();
}
}
protected: protected:
/** /**
@ -192,7 +218,7 @@ public:
void startBuilder() override; void startBuilder() override;
std::variant<BuildError, SingleDrvOutputs> unprepareBuild() override; SingleDrvOutputs unprepareBuild() override;
protected: protected:
@ -286,9 +312,11 @@ private:
*/ */
void startDaemon(); void startDaemon();
public: /**
* Stop the in-process nix daemon thread.
void stopDaemon() override; * @see startDaemon
*/
void stopDaemon();
protected: protected:
@ -343,15 +371,25 @@ private:
*/ */
SingleDrvOutputs registerOutputs(); SingleDrvOutputs registerOutputs();
public:
void deleteTmpDir(bool force) override;
void killSandbox(bool getStats) override;
protected: protected:
virtual void cleanupBuild(); /**
* Delete the temporary directory, if we have one.
*
* @param force We know the build suceeded, so don't attempt to
* preseve anything for debugging.
*/
virtual void cleanupBuild(bool force);
/**
* Kill any processes running under the build user UID or in the
* cgroup of the build.
*/
virtual void killSandbox(bool getStats);
public:
bool killChild() override;
private: private:
@ -414,6 +452,24 @@ void DerivationBuilderImpl::killSandbox(bool getStats)
} }
} }
bool DerivationBuilderImpl::killChild()
{
bool ret = pid != -1;
if (ret) {
/* If we're using a build user, then there is a tricky race
condition: if we kill the build user before the child has
done its setuid() to the build user uid, then it won't be
killed, and we'll potentially lock up in pid.wait(). So
also send a conventional kill to the child. */
::kill(-pid, SIGKILL); /* ignore the result */
killSandbox(true);
pid.wait();
}
return ret;
}
bool DerivationBuilderImpl::prepareBuild() bool DerivationBuilderImpl::prepareBuild()
{ {
if (useBuildUsers()) { if (useBuildUsers()) {
@ -427,7 +483,7 @@ bool DerivationBuilderImpl::prepareBuild()
return true; return true;
} }
std::variant<BuildError, SingleDrvOutputs> DerivationBuilderImpl::unprepareBuild() SingleDrvOutputs DerivationBuilderImpl::unprepareBuild()
{ {
// FIXME: get rid of this, rely on RAII. // FIXME: get rid of this, rely on RAII.
Finally releaseBuildUser([&]() { Finally releaseBuildUser([&]() {
@ -476,56 +532,42 @@ std::variant<BuildError, SingleDrvOutputs> DerivationBuilderImpl::unprepareBuild
((double) buildResult.cpuSystem->count()) / 1000000); ((double) buildResult.cpuSystem->count()) / 1000000);
} }
bool diskFull = false; /* Check the exit status. */
if (!statusOk(status)) {
try { bool diskFull = decideWhetherDiskFull();
/* Check the exit status. */ cleanupBuild(false);
if (!statusOk(status)) {
diskFull |= decideWhetherDiskFull(); auto msg =
fmt("Cannot build '%s'.\n"
"Reason: " ANSI_RED "builder %s" ANSI_NORMAL ".",
Magenta(store.printStorePath(drvPath)),
statusToString(status));
cleanupBuild(); msg += showKnownOutputs(store, drv);
auto msg = miscMethods->appendLogTailErrorMsg(msg);
fmt("Cannot build '%s'.\n"
"Reason: " ANSI_RED "builder %s" ANSI_NORMAL ".",
Magenta(store.printStorePath(drvPath)),
statusToString(status));
msg += showKnownOutputs(store, drv); if (diskFull)
msg += "\nnote: build failure may have been caused by lack of free disk space";
miscMethods->appendLogTailErrorMsg(msg); throw BuildError(
!derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure : BuildResult::PermanentFailure,
if (diskFull) msg);
msg += "\nnote: build failure may have been caused by lack of free disk space";
throw BuildError(
!derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure
: BuildResult::PermanentFailure,
msg);
}
/* Compute the FS closure of the outputs and register them as
being valid. */
auto builtOutputs = registerOutputs();
/* Delete unused redirected outputs (when doing hash rewriting). */
for (auto & i : redirectedOutputs)
deletePath(store.Store::toRealPath(i.second));
deleteTmpDir(true);
return std::move(builtOutputs);
} catch (BuildError & e) {
return std::move(e);
} }
}
void DerivationBuilderImpl::cleanupBuild() /* Compute the FS closure of the outputs and register them as
{ being valid. */
deleteTmpDir(false); auto builtOutputs = registerOutputs();
/* Delete unused redirected outputs (when doing hash rewriting). */
for (auto & i : redirectedOutputs)
deletePath(store.Store::toRealPath(i.second));
cleanupBuild(true);
return builtOutputs;
} }
static void chmod_(const Path & path, mode_t mode) static void chmod_(const Path & path, mode_t mode)
@ -1757,7 +1799,6 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
} }
store.optimisePath(actualPath, NoRepair); // FIXME: combine with scanForReferences() store.optimisePath(actualPath, NoRepair); // FIXME: combine with scanForReferences()
miscMethods->markContentsGood(newInfo.path);
newInfo.deriver = drvPath; newInfo.deriver = drvPath;
newInfo.ultimate = true; newInfo.ultimate = true;
@ -1825,7 +1866,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
return builtOutputs; return builtOutputs;
} }
void DerivationBuilderImpl::deleteTmpDir(bool force) void DerivationBuilderImpl::cleanupBuild(bool force)
{ {
if (topTmpDir != "") { if (topTmpDir != "") {
/* As an extra precaution, even in the event of `deletePath` failing to /* As an extra precaution, even in the event of `deletePath` failing to

View file

@ -659,7 +659,7 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu
throw SysError("setuid failed"); throw SysError("setuid failed");
} }
std::variant<BuildError, SingleDrvOutputs> unprepareBuild() override SingleDrvOutputs unprepareBuild() override
{ {
sandboxMountNamespace = -1; sandboxMountNamespace = -1;
sandboxUserNamespace = -1; sandboxUserNamespace = -1;