libstore: use vfork for linux sandbox launch wrappers

this halves sandbox launch overhead, reducing the build time for 3000
trivial runCommand derivations on our machine from 80 seconds to 44s.
as a nice side effect we also get better error message propagation in
some cases, most notably setgroups failing when run in lix sandboxes.

Change-Id: Ia7c50a844915d9b8a20475b90b2d0179fd2fff34
This commit is contained in:
eldritch horrors
2026-01-28 15:01:13 +00:00
parent bf3d4e8721
commit 56988d8605
3 changed files with 24 additions and 25 deletions
@@ -0,0 +1,12 @@
---
synopsis: "Linux sandbox launch overhead greatly reduced"
cls: [5030]
category: "Improvements"
credits: [horrors]
---
Sandboxed builds are now much cheaper to launch on Linux with 50% lower management
overhead. This will mostly be noticeable when building derivation trees containing
many small derivations like nixpkgs' `writeFile` or `runCommand` with scripts that
very quickly. In synthetic tests we have seen build times of 3000 small runCommand
drop from 80 seconds to 44 seconds, which is the most optimistic case in practice.
+10 -23
View File
@@ -1471,15 +1471,10 @@ Pid LinuxLocalDerivationGoal::startChild(
return {std::move(userns), std::move(netns)};
}();
Pipe sendPid;
sendPid.create();
Pid helper = startProcess([&]() {
Pid pid = inVFork(/* flags*/ 0, [&]() {
if (prctl(PR_SET_PDEATHSIG, SIGKILL) == -1)
throw SysError("setting death signal");
sendPid.readSide.close();
if (dup2(logPTY.get(), STDERR_FILENO) == -1) {
throw SysError("failed to redirect build output to log file");
}
@@ -1504,25 +1499,17 @@ Pid LinuxLocalDerivationGoal::startChild(
options.cloneFlags =
CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
pid_t child = startProcess([&]() {
if (prctl(PR_SET_PDEATHSIG, SIGKILL) == -1)
throw SysError("setting death signal");
runChild(builder, envStrs, args);
}, options).release();
writeFull(sendPid.writeSide.get(), fmt("%d\n", child));
_exit(0);
return startProcess(
[&]() {
if (prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) {
throw SysError("setting death signal");
}
runChild(builder, envStrs, args);
},
options
);
});
sendPid.writeSide.close();
if (helper.wait() != 0)
throw Error("unable to start build process");
auto ss = tokenizeString<std::vector<std::string>>(readLine(sendPid.readSide.get()));
assert(ss.size() == 1);
Pid pid = Pid{string2Int<pid_t>(ss[0]).value()};
/* Migrate the child inside the available control group. */
if (context.cgroup) {
context.cgroup->adoptProcess(pid.get());
+2 -2
View File
@@ -21,13 +21,13 @@ unshare --mount --map-root-user bash <<EOF
# Fails with default setting
# TODO better error
setLocalStore store1
expectStderr 1 "\${cmd[@]}" | grepQuiet "unable to start build process"
expectStderr 1 "\${cmd[@]}" | grepQuiet "setgroups failed"
# Fails with `require-drop-supplementary-groups`
# TODO better error
setLocalStore store2
NIX_CONFIG='require-drop-supplementary-groups = true' \
expectStderr 1 "\${cmd[@]}" | grepQuiet "unable to start build process"
expectStderr 1 "\${cmd[@]}" | grepQuiet "setgroups failed"
# Works without `require-drop-supplementary-groups`
setLocalStore store3