Kill builds when we get EOF on the log FD
This closes a long-time bug that allowed builds to hang Nix indefinitely (regardless of timeouts) simply by doing exec > /dev/null 2>&1; while true; do true; done Now, on EOF, we just send SIGKILL to the child to make sure it's really gone.
This commit is contained in:
parent
63e10b4d28
commit
21948deed9
6 changed files with 40 additions and 40 deletions
|
@ -333,7 +333,7 @@ RunPager::~RunPager()
|
||||||
if (pid != -1) {
|
if (pid != -1) {
|
||||||
std::cout.flush();
|
std::cout.flush();
|
||||||
close(STDOUT_FILENO);
|
close(STDOUT_FILENO);
|
||||||
pid.wait(true);
|
pid.wait();
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreException();
|
||||||
|
|
|
@ -646,7 +646,7 @@ HookInstance::~HookInstance()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
toHook.writeSide = -1;
|
toHook.writeSide = -1;
|
||||||
pid.kill(true);
|
if (pid != -1) pid.kill(true);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreException();
|
||||||
}
|
}
|
||||||
|
@ -960,7 +960,7 @@ void DerivationGoal::killChild()
|
||||||
child. */
|
child. */
|
||||||
::kill(-pid, SIGKILL); /* ignore the result */
|
::kill(-pid, SIGKILL); /* ignore the result */
|
||||||
buildUser.kill();
|
buildUser.kill();
|
||||||
pid.wait(true);
|
pid.wait();
|
||||||
} else
|
} else
|
||||||
pid.kill();
|
pid.kill();
|
||||||
|
|
||||||
|
@ -1416,11 +1416,9 @@ void DerivationGoal::buildDone()
|
||||||
|
|
||||||
/* Since we got an EOF on the logger pipe, the builder is presumed
|
/* Since we got an EOF on the logger pipe, the builder is presumed
|
||||||
to have terminated. In fact, the builder could also have
|
to have terminated. In fact, the builder could also have
|
||||||
simply have closed its end of the pipe --- just don't do that
|
simply have closed its end of the pipe, so just to be sure,
|
||||||
:-) */
|
kill it. */
|
||||||
/* !!! this could block! security problem! solution: kill the
|
int status = hook ? hook->pid.kill(true) : pid.kill(true);
|
||||||
child */
|
|
||||||
int status = hook ? hook->pid.wait(true) : pid.wait(true);
|
|
||||||
|
|
||||||
debug(format("builder process for ‘%1%’ finished") % drvPath);
|
debug(format("builder process for ‘%1%’ finished") % drvPath);
|
||||||
|
|
||||||
|
@ -2112,6 +2110,8 @@ void DerivationGoal::startBuilder()
|
||||||
result.startTime = time(0);
|
result.startTime = time(0);
|
||||||
|
|
||||||
/* Fork a child to build the package. */
|
/* Fork a child to build the package. */
|
||||||
|
ProcessOptions options;
|
||||||
|
|
||||||
#if __linux__
|
#if __linux__
|
||||||
if (useChroot) {
|
if (useChroot) {
|
||||||
/* Set up private namespaces for the build:
|
/* Set up private namespaces for the build:
|
||||||
|
@ -2153,7 +2153,6 @@ void DerivationGoal::startBuilder()
|
||||||
|
|
||||||
userNamespaceSync.create();
|
userNamespaceSync.create();
|
||||||
|
|
||||||
ProcessOptions options;
|
|
||||||
options.allowVfork = false;
|
options.allowVfork = false;
|
||||||
|
|
||||||
Pid helper = startProcess([&]() {
|
Pid helper = startProcess([&]() {
|
||||||
|
@ -2189,7 +2188,7 @@ void DerivationGoal::startBuilder()
|
||||||
_exit(0);
|
_exit(0);
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
if (helper.wait(true) != 0)
|
if (helper.wait() != 0)
|
||||||
throw Error("unable to start build process");
|
throw Error("unable to start build process");
|
||||||
|
|
||||||
userNamespaceSync.readSide = -1;
|
userNamespaceSync.readSide = -1;
|
||||||
|
@ -2220,7 +2219,6 @@ void DerivationGoal::startBuilder()
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
ProcessOptions options;
|
|
||||||
options.allowVfork = !buildUser.enabled() && !drv->isBuiltin();
|
options.allowVfork = !buildUser.enabled() && !drv->isBuiltin();
|
||||||
pid = startProcess([&]() {
|
pid = startProcess([&]() {
|
||||||
runChild();
|
runChild();
|
||||||
|
@ -3702,7 +3700,8 @@ void Worker::waitForInput()
|
||||||
|
|
||||||
auto after = steady_time_point::clock::now();
|
auto after = steady_time_point::clock::now();
|
||||||
|
|
||||||
/* Process all available file descriptors. */
|
/* Process all available file descriptors. FIXME: this is
|
||||||
|
O(children * fds). */
|
||||||
decltype(children)::iterator i;
|
decltype(children)::iterator i;
|
||||||
for (auto j = children.begin(); j != children.end(); j = i) {
|
for (auto j = children.begin(); j != children.end(); j = i) {
|
||||||
i = std::next(j);
|
i = std::next(j);
|
||||||
|
|
|
@ -648,26 +648,25 @@ void Pipe::create()
|
||||||
|
|
||||||
|
|
||||||
Pid::Pid()
|
Pid::Pid()
|
||||||
: pid(-1), separatePG(false), killSignal(SIGKILL)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Pid::Pid(pid_t pid)
|
Pid::Pid(pid_t pid)
|
||||||
: pid(pid), separatePG(false), killSignal(SIGKILL)
|
: pid(pid)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Pid::~Pid()
|
Pid::~Pid()
|
||||||
{
|
{
|
||||||
kill();
|
if (pid != -1) kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Pid::operator =(pid_t pid)
|
void Pid::operator =(pid_t pid)
|
||||||
{
|
{
|
||||||
if (this->pid != pid) kill();
|
if (this->pid != -1 && this->pid != pid) kill();
|
||||||
this->pid = pid;
|
this->pid = pid;
|
||||||
killSignal = SIGKILL; // reset signal to default
|
killSignal = SIGKILL; // reset signal to default
|
||||||
}
|
}
|
||||||
|
@ -679,9 +678,9 @@ Pid::operator pid_t()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Pid::kill(bool quiet)
|
int Pid::kill(bool quiet)
|
||||||
{
|
{
|
||||||
if (pid == -1 || pid == 0) return;
|
assert(pid != -1);
|
||||||
|
|
||||||
if (!quiet)
|
if (!quiet)
|
||||||
printError(format("killing process %1%") % pid);
|
printError(format("killing process %1%") % pid);
|
||||||
|
@ -692,32 +691,20 @@ void Pid::kill(bool quiet)
|
||||||
if (::kill(separatePG ? -pid : pid, killSignal) != 0)
|
if (::kill(separatePG ? -pid : pid, killSignal) != 0)
|
||||||
printError((SysError(format("killing process %1%") % pid).msg()));
|
printError((SysError(format("killing process %1%") % pid).msg()));
|
||||||
|
|
||||||
/* Wait until the child dies, disregarding the exit status. */
|
return wait();
|
||||||
int status;
|
|
||||||
while (waitpid(pid, &status, 0) == -1) {
|
|
||||||
checkInterrupt();
|
|
||||||
if (errno != EINTR) {
|
|
||||||
printError(
|
|
||||||
(SysError(format("waiting for process %1%") % pid).msg()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pid = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Pid::wait(bool block)
|
int Pid::wait()
|
||||||
{
|
{
|
||||||
assert(pid != -1);
|
assert(pid != -1);
|
||||||
while (1) {
|
while (1) {
|
||||||
int status;
|
int status;
|
||||||
int res = waitpid(pid, &status, block ? 0 : WNOHANG);
|
int res = waitpid(pid, &status, 0);
|
||||||
if (res == pid) {
|
if (res == pid) {
|
||||||
pid = -1;
|
pid = -1;
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
if (res == 0 && !block) return -1;
|
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
throw SysError("cannot get child exit status");
|
throw SysError("cannot get child exit status");
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
@ -782,7 +769,7 @@ void killUser(uid_t uid)
|
||||||
_exit(0);
|
_exit(0);
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
int status = pid.wait(true);
|
int status = pid.wait();
|
||||||
if (status != 0)
|
if (status != 0)
|
||||||
throw Error(format("cannot kill processes for uid ‘%1%’: %2%") % uid % statusToString(status));
|
throw Error(format("cannot kill processes for uid ‘%1%’: %2%") % uid % statusToString(status));
|
||||||
|
|
||||||
|
@ -893,7 +880,7 @@ string runProgram(Path program, bool searchPath, const Strings & args,
|
||||||
string result = drainFD(out.readSide.get());
|
string result = drainFD(out.readSide.get());
|
||||||
|
|
||||||
/* Wait for the child to finish. */
|
/* Wait for the child to finish. */
|
||||||
int status = pid.wait(true);
|
int status = pid.wait();
|
||||||
if (!statusOk(status))
|
if (!statusOk(status))
|
||||||
throw ExecError(status, format("program ‘%1%’ %2%")
|
throw ExecError(status, format("program ‘%1%’ %2%")
|
||||||
% program % statusToString(status));
|
% program % statusToString(status));
|
||||||
|
|
|
@ -192,17 +192,18 @@ typedef std::unique_ptr<DIR, DIRDeleter> AutoCloseDir;
|
||||||
|
|
||||||
class Pid
|
class Pid
|
||||||
{
|
{
|
||||||
pid_t pid;
|
pid_t pid = -1;
|
||||||
bool separatePG;
|
bool separatePG = false;
|
||||||
int killSignal;
|
int killSignal = SIGKILL;
|
||||||
public:
|
public:
|
||||||
Pid();
|
Pid();
|
||||||
Pid(pid_t pid);
|
Pid(pid_t pid);
|
||||||
~Pid();
|
~Pid();
|
||||||
void operator =(pid_t pid);
|
void operator =(pid_t pid);
|
||||||
operator pid_t();
|
operator pid_t();
|
||||||
void kill(bool quiet = false);
|
int kill(bool quiet = false);
|
||||||
int wait(bool block);
|
int wait();
|
||||||
|
|
||||||
void setSeparatePG(bool separatePG);
|
void setSeparatePG(bool separatePG);
|
||||||
void setKillSignal(int signal);
|
void setKillSignal(int signal);
|
||||||
pid_t release();
|
pid_t release();
|
||||||
|
|
|
@ -17,4 +17,12 @@ with import ./config.nix;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
closeLog = mkDerivation {
|
||||||
|
name = "silent";
|
||||||
|
buildCommand = ''
|
||||||
|
exec > /dev/null 2>&1
|
||||||
|
sleep 1000000000
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,3 +24,8 @@ if nix-build timeout.nix -A silent --max-silent-time 2; then
|
||||||
echo "build should have failed"
|
echo "build should have failed"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if nix-build timeout.nix -A closeLog; then
|
||||||
|
echo "build should have failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
Loading…
Reference in a new issue