forked from lix-project/lix
* Kill a build if it has gone for more than a certain number of
seconds without producing output on stdout or stderr (NIX-65). This timeout can be specified using the `--max-silent-time' option or the `build-max-silent-time' configuration setting. The default is infinity (0). * Fix 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.
This commit is contained in:
parent
d3fe6ab024
commit
9dbfe242e3
|
@ -37,6 +37,11 @@
|
||||||
<listitem><para>TODO: magic <varname>exportReferencesGraph</varname>
|
<listitem><para>TODO: magic <varname>exportReferencesGraph</varname>
|
||||||
attribute.</para></listitem>
|
attribute.</para></listitem>
|
||||||
|
|
||||||
|
|
||||||
|
<listitem><para>TODO: option <option>--max-silent-time</option>,
|
||||||
|
configuration setting
|
||||||
|
<literal>build-max-silent-time</literal>.</para></listitem>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
|
|
|
@ -78,6 +78,23 @@
|
||||||
#build-max-jobs = 1
|
#build-max-jobs = 1
|
||||||
|
|
||||||
|
|
||||||
|
### Option `build-max-silent-time'
|
||||||
|
#
|
||||||
|
# This option defines the maximum number of seconds that builder can
|
||||||
|
# go without producing any data on standard output or standard error.
|
||||||
|
# This is useful (for instance in a automated build system) to catch
|
||||||
|
# builds that are stuck in an infinite loop, or to catch remote builds
|
||||||
|
# that are hanging due to network problems. It can be overriden using
|
||||||
|
# the `--max-silent-time' command line switch.
|
||||||
|
#
|
||||||
|
# The value 0 means that there is no timeout. This is also the
|
||||||
|
# default.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# build-max-silent-time = 600 # = 10 minutes
|
||||||
|
#build-max-silent-time = 0
|
||||||
|
|
||||||
|
|
||||||
### Option `build-users-group'
|
### Option `build-users-group'
|
||||||
#
|
#
|
||||||
# This options specifies the Unix group containing the Nix build user
|
# This options specifies the Unix group containing the Nix build user
|
||||||
|
|
|
@ -83,6 +83,12 @@ EOF
|
||||||
$n += 2;
|
$n += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elsif ($arg eq "--max-jobs" or $arg eq "-j" or $arg eq "--max-silent-time") {
|
||||||
|
$n++;
|
||||||
|
die "$0: `$arg' requires an argument\n" unless $n < scalar @ARGV;
|
||||||
|
push @buildArgs, ($arg, $ARGV[$n]);
|
||||||
|
}
|
||||||
|
|
||||||
elsif (substr($arg, 0, 1) eq "-") {
|
elsif (substr($arg, 0, 1) eq "-") {
|
||||||
push @buildArgs, $arg;
|
push @buildArgs, $arg;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,18 @@ static void setLogType(string lt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static unsigned int getIntArg(const string & opt,
|
||||||
|
Strings::iterator & i, const Strings::iterator & end)
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
if (i == end) throw UsageError(format("`%1%' requires an argument") % opt);
|
||||||
|
int n;
|
||||||
|
if (!string2Int(*i, n) || n < 0)
|
||||||
|
throw UsageError(format("`%1%' requires a non-negative integer") % opt);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
struct RemoveTempRoots
|
struct RemoveTempRoots
|
||||||
{
|
{
|
||||||
~RemoveTempRoots()
|
~RemoveTempRoots()
|
||||||
|
@ -91,12 +103,8 @@ static void initAndRun(int argc, char * * argv)
|
||||||
|
|
||||||
/* Get some settings from the configuration file. */
|
/* Get some settings from the configuration file. */
|
||||||
thisSystem = querySetting("system", SYSTEM);
|
thisSystem = querySetting("system", SYSTEM);
|
||||||
{
|
maxBuildJobs = queryIntSetting("build-max-jobs", 1);
|
||||||
int n;
|
maxSilentTime = queryIntSetting("build-max-silent-time", 0);
|
||||||
if (!string2Int(querySetting("build-max-jobs", "1"), n) || n < 0)
|
|
||||||
throw Error("invalid value for configuration setting `build-max-jobs'");
|
|
||||||
maxBuildJobs = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Catch SIGINT. */
|
/* Catch SIGINT. */
|
||||||
struct sigaction act, oact;
|
struct sigaction act, oact;
|
||||||
|
@ -180,16 +188,12 @@ static void initAndRun(int argc, char * * argv)
|
||||||
keepGoing = true;
|
keepGoing = true;
|
||||||
else if (arg == "--fallback")
|
else if (arg == "--fallback")
|
||||||
tryFallback = true;
|
tryFallback = true;
|
||||||
else if (arg == "--max-jobs" || arg == "-j") {
|
else if (arg == "--max-jobs" || arg == "-j")
|
||||||
++i;
|
maxBuildJobs = getIntArg(arg, i, args.end());
|
||||||
if (i == args.end()) throw UsageError("`--max-jobs' requires an argument");
|
|
||||||
int n;
|
|
||||||
if (!string2Int(*i, n) || n < 0)
|
|
||||||
throw UsageError(format("`--max-jobs' requires a non-negative integer"));
|
|
||||||
maxBuildJobs = n;
|
|
||||||
}
|
|
||||||
else if (arg == "--readonly-mode")
|
else if (arg == "--readonly-mode")
|
||||||
readOnlyMode = true;
|
readOnlyMode = true;
|
||||||
|
else if (arg == "--max-silent-time")
|
||||||
|
maxSilentTime = getIntArg(arg, i, args.end());
|
||||||
else remaining.push_back(arg);
|
else remaining.push_back(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <boost/weak_ptr.hpp>
|
#include <boost/weak_ptr.hpp>
|
||||||
#include <boost/enable_shared_from_this.hpp>
|
#include <boost/enable_shared_from_this.hpp>
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
@ -135,6 +136,7 @@ struct Child
|
||||||
WeakGoalPtr goal;
|
WeakGoalPtr goal;
|
||||||
set<int> fds;
|
set<int> fds;
|
||||||
bool inBuildSlot;
|
bool inBuildSlot;
|
||||||
|
time_t lastOutput; /* time we last got output on stdout/stderr */
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef map<pid_t, Child> Children;
|
typedef map<pid_t, Child> Children;
|
||||||
|
@ -660,9 +662,18 @@ DerivationGoal::~DerivationGoal()
|
||||||
worker.childTerminated(pid);
|
worker.childTerminated(pid);
|
||||||
|
|
||||||
if (buildUser.enabled()) {
|
if (buildUser.enabled()) {
|
||||||
/* Can't let pid's destructor do it, since it may not
|
/* Note that we can't let pid's destructor kill the
|
||||||
have the appropriate privilege (i.e., the setuid
|
the child process, since it may not have the
|
||||||
helper should do it). */
|
appropriate privilege (i.e., the setuid helper
|
||||||
|
should do it).
|
||||||
|
|
||||||
|
However, 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 */
|
||||||
buildUser.kill();
|
buildUser.kill();
|
||||||
pid.wait(true);
|
pid.wait(true);
|
||||||
assert(pid == -1);
|
assert(pid == -1);
|
||||||
|
@ -2156,6 +2167,7 @@ void Worker::childStarted(GoalPtr goal,
|
||||||
Child child;
|
Child child;
|
||||||
child.goal = goal;
|
child.goal = goal;
|
||||||
child.fds = fds;
|
child.fds = fds;
|
||||||
|
child.lastOutput = time(0);
|
||||||
child.inBuildSlot = inBuildSlot;
|
child.inBuildSlot = inBuildSlot;
|
||||||
children[pid] = child;
|
children[pid] = child;
|
||||||
if (inBuildSlot) nrChildren++;
|
if (inBuildSlot) nrChildren++;
|
||||||
|
@ -2255,6 +2267,24 @@ void Worker::waitForInput()
|
||||||
the logger pipe of a build, we assume that the builder has
|
the logger pipe of a build, we assume that the builder has
|
||||||
terminated. */
|
terminated. */
|
||||||
|
|
||||||
|
/* If we're monitoring for silence on stdout/stderr, sleep until
|
||||||
|
the first deadline for any child. */
|
||||||
|
struct timeval timeout;
|
||||||
|
if (maxSilentTime != 0) {
|
||||||
|
time_t oldest = 0;
|
||||||
|
for (Children::iterator i = children.begin();
|
||||||
|
i != children.end(); ++i)
|
||||||
|
{
|
||||||
|
oldest = oldest == 0 || i->second.lastOutput < oldest
|
||||||
|
? i->second.lastOutput : oldest;
|
||||||
|
}
|
||||||
|
time_t now = time(0);
|
||||||
|
timeout.tv_sec = (time_t) (oldest + maxSilentTime) <= now ? 0 :
|
||||||
|
oldest + maxSilentTime - now;
|
||||||
|
timeout.tv_usec = 0;
|
||||||
|
printMsg(lvlVomit, format("sleeping %1% seconds") % timeout.tv_sec);
|
||||||
|
}
|
||||||
|
|
||||||
/* Use select() to wait for the input side of any logger pipe to
|
/* Use select() to wait for the input side of any logger pipe to
|
||||||
become `available'. Note that `available' (i.e., non-blocking)
|
become `available'. Note that `available' (i.e., non-blocking)
|
||||||
includes EOF. */
|
includes EOF. */
|
||||||
|
@ -2272,11 +2302,13 @@ void Worker::waitForInput()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (select(fdMax, &fds, 0, 0, 0) == -1) {
|
if (select(fdMax, &fds, 0, 0, maxSilentTime != 0 ? &timeout : 0) == -1) {
|
||||||
if (errno == EINTR) return;
|
if (errno == EINTR) return;
|
||||||
throw SysError("waiting for input");
|
throw SysError("waiting for input");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time_t now = time(0);
|
||||||
|
|
||||||
/* Process all available file descriptors. */
|
/* Process all available file descriptors. */
|
||||||
for (Children::iterator i = children.begin();
|
for (Children::iterator i = children.begin();
|
||||||
i != children.end(); ++i)
|
i != children.end(); ++i)
|
||||||
|
@ -2284,9 +2316,9 @@ void Worker::waitForInput()
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
GoalPtr goal = i->second.goal.lock();
|
GoalPtr goal = i->second.goal.lock();
|
||||||
assert(goal);
|
assert(goal);
|
||||||
|
|
||||||
set<int> fds2(i->second.fds);
|
set<int> fds2(i->second.fds);
|
||||||
for (set<int>::iterator j = fds2.begin(); j != fds2.end(); ++j)
|
for (set<int>::iterator j = fds2.begin(); j != fds2.end(); ++j) {
|
||||||
{
|
|
||||||
if (FD_ISSET(*j, &fds)) {
|
if (FD_ISSET(*j, &fds)) {
|
||||||
unsigned char buffer[4096];
|
unsigned char buffer[4096];
|
||||||
ssize_t rd = read(*j, buffer, sizeof(buffer));
|
ssize_t rd = read(*j, buffer, sizeof(buffer));
|
||||||
|
@ -2303,9 +2335,15 @@ void Worker::waitForInput()
|
||||||
% goal->getName() % rd);
|
% goal->getName() % rd);
|
||||||
string data((char *) buffer, rd);
|
string data((char *) buffer, rd);
|
||||||
goal->handleChildOutput(*j, data);
|
goal->handleChildOutput(*j, data);
|
||||||
|
i->second.lastOutput = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (maxSilentTime != 0 &&
|
||||||
|
now - i->second.lastOutput >= (time_t) maxSilentTime)
|
||||||
|
throw Error(format("%1% timed out after %2% seconds of silence")
|
||||||
|
% goal->getName() % maxSilentTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ Verbosity buildVerbosity = lvlInfo;
|
||||||
unsigned int maxBuildJobs = 1;
|
unsigned int maxBuildJobs = 1;
|
||||||
bool readOnlyMode = false;
|
bool readOnlyMode = false;
|
||||||
string thisSystem = "unset";
|
string thisSystem = "unset";
|
||||||
|
unsigned int maxSilentTime = 0;
|
||||||
|
|
||||||
|
|
||||||
static bool settingsRead = false;
|
static bool settingsRead = false;
|
||||||
|
@ -104,5 +105,14 @@ bool queryBoolSetting(const string & name, bool def)
|
||||||
% name % v);
|
% name % v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unsigned int queryIntSetting(const string & name, unsigned int def)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
if (!string2Int(querySetting(name, int2String(def)), n) || n < 0)
|
||||||
|
throw Error(format("configuration setting `%1%' should have an integer value") % name);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,11 @@ extern bool readOnlyMode;
|
||||||
/* The canonical system name, as returned by config.guess. */
|
/* The canonical system name, as returned by config.guess. */
|
||||||
extern string thisSystem;
|
extern string thisSystem;
|
||||||
|
|
||||||
|
/* The maximum time in seconds that a builer can go without producing
|
||||||
|
any output on stdout/stderr before it is killed. 0 means
|
||||||
|
infinity. */
|
||||||
|
extern unsigned int maxSilentTime;
|
||||||
|
|
||||||
|
|
||||||
Strings querySetting(const string & name, const Strings & def);
|
Strings querySetting(const string & name, const Strings & def);
|
||||||
|
|
||||||
|
@ -69,6 +74,8 @@ string querySetting(const string & name, const string & def);
|
||||||
|
|
||||||
bool queryBoolSetting(const string & name, bool def);
|
bool queryBoolSetting(const string & name, bool def);
|
||||||
|
|
||||||
|
unsigned int queryIntSetting(const string & name, unsigned int def);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue