Add support for the build-timeout' and --timeout' options.

This commit is contained in:
Ludovic Courtès 2011-06-30 15:19:13 +00:00
parent 9c99aa2620
commit 5c9e9f732d
12 changed files with 115 additions and 5 deletions

View File

@ -134,6 +134,23 @@ env-keep-derivations = false
</listitem> </listitem>
<varlistentry xml:id="conf-build-timeout"><term><literal>build-timeout</literal></term>
<listitem>
<para>This option defines the maximum number of seconds that a
builder can run. This is useful (for instance in a automated
build system) to catch builds that are stuck in an infinite loop
but keep writing to their standard output or standard error. It
can be overriden using the <option
linkend="opt-timeout">--timeout</option> command line
switch.</para>
<para>The value <literal>0</literal> means that there is no
timeout. This is also the default.</para>
</listitem>
</varlistentry> </varlistentry>

View File

@ -21,6 +21,10 @@
<arg><option>--max-silent-time</option></arg> <arg><option>--max-silent-time</option></arg>
<replaceable>number</replaceable> <replaceable>number</replaceable>
</arg> </arg>
<arg>
<arg><option>--timeout</option></arg>
<replaceable>number</replaceable>
</arg>
<arg><option>--keep-going</option></arg> <arg><option>--keep-going</option></arg>
<arg><option>-k</option></arg> <arg><option>-k</option></arg>
<arg><option>--keep-failed</option></arg> <arg><option>--keep-failed</option></arg>

View File

@ -132,6 +132,16 @@
</varlistentry> </varlistentry>
<varlistentry xml:id="opt-timeout"><term><option>--timeout</option></term>
<listitem><para>Sets the maximum number of seconds that a builder
can run. The default is specified by the <link
linkend='conf-build-timeout'><literal>build-timeout</literal></link>
configuration setting. <literal>0</literal> means no
timeout.</para></listitem>
</varlistentry>
<varlistentry><term><option>--keep-going</option></term> <varlistentry><term><option>--keep-going</option></term>
<term><option>-k</option></term> <term><option>-k</option></term>

View File

@ -22,6 +22,16 @@
option.</para> option.</para>
</listitem> </listitem>
<listitem>
<para>The option <option>--timeout</option> (corresponding to the
configuration setting <literal>build-timeout</literal>) allows you
to set an absolute timeout on builds — if a build runs for more than
the given number of seconds, it is terminated. This is useful for
recovering automatically from builds that are stuck in an infinite
loop but keep producing output, and for which
<literal>--max-silent-time</literal> is ineffective.</para>
</listitem>
</itemizedlist> </itemizedlist>
</section> </section>

View File

@ -142,6 +142,7 @@ static void initAndRun(int argc, char * * argv)
maxBuildJobs = queryIntSetting("build-max-jobs", 1); maxBuildJobs = queryIntSetting("build-max-jobs", 1);
buildCores = queryIntSetting("build-cores", 1); buildCores = queryIntSetting("build-cores", 1);
maxSilentTime = queryIntSetting("build-max-silent-time", 0); maxSilentTime = queryIntSetting("build-max-silent-time", 0);
buildTimeout = queryIntSetting("build-timeout", 0);
/* Catch SIGINT. */ /* Catch SIGINT. */
struct sigaction act; struct sigaction act;
@ -237,6 +238,8 @@ static void initAndRun(int argc, char * * argv)
readOnlyMode = true; readOnlyMode = true;
else if (arg == "--max-silent-time") else if (arg == "--max-silent-time")
maxSilentTime = getIntArg<unsigned int>(arg, i, args.end()); maxSilentTime = getIntArg<unsigned int>(arg, i, args.end());
else if (arg == "--timeout")
buildTimeout = getIntArg<unsigned int>(arg, i, args.end());
else if (arg == "--no-build-hook") else if (arg == "--no-build-hook")
useBuildHook = false; useBuildHook = false;
else if (arg == "--show-trace") else if (arg == "--show-trace")

View File

@ -210,6 +210,9 @@ private:
/* Last time the goals in `waitingForAWhile' where woken up. */ /* Last time the goals in `waitingForAWhile' where woken up. */
time_t lastWokenUp; time_t lastWokenUp;
/* Last time `waitForInput' was last called. */
time_t lastWait;
public: public:
bool cacheFailure; bool cacheFailure;
@ -682,6 +685,7 @@ HookInstance::HookInstance()
if (dup2(builderOut.writeSide, 4) == -1) if (dup2(builderOut.writeSide, 4) == -1)
throw SysError("dupping builder's stdout/stderr"); throw SysError("dupping builder's stdout/stderr");
/* XXX: Pass `buildTimeout' to the hook? */
execl(buildHook.c_str(), buildHook.c_str(), thisSystem.c_str(), execl(buildHook.c_str(), buildHook.c_str(), thisSystem.c_str(),
(format("%1%") % maxSilentTime).str().c_str(), (format("%1%") % maxSilentTime).str().c_str(),
(format("%1%") % printBuildTrace).str().c_str(), (format("%1%") % printBuildTrace).str().c_str(),
@ -2667,6 +2671,13 @@ void Worker::waitForInput()
timeout.tv_usec = 0; timeout.tv_usec = 0;
time_t before = time(0); time_t before = time(0);
/* If a global timeout has been set, sleep until it's done. */
if (buildTimeout != 0) {
useTimeout = true;
if (lastWait == 0 || lastWait > before) lastWait = before;
timeout.tv_sec = std::max((time_t) 0, lastWait + buildTimeout - before);
}
/* If we're monitoring for silence on stdout/stderr, sleep until /* If we're monitoring for silence on stdout/stderr, sleep until
the first deadline for any child. */ the first deadline for any child. */
if (maxSilentTime != 0) { if (maxSilentTime != 0) {
@ -2678,8 +2689,11 @@ void Worker::waitForInput()
} }
} }
if (oldest) { if (oldest) {
time_t silenceTimeout = std::max((time_t) 0, oldest + maxSilentTime - before);
timeout.tv_sec = useTimeout
? std::min(silenceTimeout, timeout.tv_sec)
: silenceTimeout;
useTimeout = true; useTimeout = true;
timeout.tv_sec = std::max((time_t) 0, oldest + maxSilentTime - before);
printMsg(lvlVomit, format("sleeping %1% seconds") % timeout.tv_sec); printMsg(lvlVomit, format("sleeping %1% seconds") % timeout.tv_sec);
} }
} }
@ -2717,6 +2731,9 @@ void Worker::waitForInput()
time_t after = time(0); time_t after = time(0);
/* Keep track of when we were last called. */
lastWait = after;
/* Process all available file descriptors. */ /* Process all available file descriptors. */
/* Since goals may be canceled from inside the loop below (causing /* Since goals may be canceled from inside the loop below (causing
@ -2765,6 +2782,15 @@ void Worker::waitForInput()
% goal->getName() % maxSilentTime); % goal->getName() % maxSilentTime);
goal->cancel(); goal->cancel();
} }
if (buildTimeout != 0 &&
after - before >= (time_t) buildTimeout)
{
printMsg(lvlError,
format("%1% timed out after %2% seconds of activity")
% goal->getName() % buildTimeout);
goal->cancel();
}
} }
if (!waitingForAWhile.empty() && lastWokenUp + wakeUpInterval <= after) { if (!waitingForAWhile.empty() && lastWokenUp + wakeUpInterval <= after) {

View File

@ -26,6 +26,7 @@ unsigned int buildCores = 1;
bool readOnlyMode = false; bool readOnlyMode = false;
string thisSystem = "unset"; string thisSystem = "unset";
time_t maxSilentTime = 0; time_t maxSilentTime = 0;
time_t buildTimeout = 0;
Paths substituters; Paths substituters;
bool useBuildHook = true; bool useBuildHook = true;
bool printBuildTrace = false; bool printBuildTrace = false;

View File

@ -72,6 +72,10 @@ extern string thisSystem;
infinity. */ infinity. */
extern time_t maxSilentTime; extern time_t maxSilentTime;
/* The maximum duration in seconds that a builder can run. 0 means
infinity. */
extern time_t buildTimeout;
/* The substituters. There are programs that can somehow realise a /* The substituters. There are programs that can somehow realise a
store path without building, e.g., by downloading it or copying it store path without building, e.g., by downloading it or copying it
from a CD. */ from a CD. */

View File

@ -8,7 +8,7 @@ TESTS = init.sh hash.sh lang.sh add.sh simple.sh dependencies.sh \
referrers.sh user-envs.sh logging.sh nix-build.sh misc.sh fixed.sh \ referrers.sh user-envs.sh logging.sh nix-build.sh misc.sh fixed.sh \
gc-runtime.sh install-package.sh check-refs.sh filter-source.sh \ gc-runtime.sh install-package.sh check-refs.sh filter-source.sh \
remote-store.sh export.sh export-graph.sh negative-caching.sh \ remote-store.sh export.sh export-graph.sh negative-caching.sh \
binary-patching.sh binary-patching.sh timeout.sh
XFAIL_TESTS = XFAIL_TESTS =
@ -33,5 +33,6 @@ EXTRA_DIST = $(TESTS) \
export-graph.nix \ export-graph.nix \
negative-caching.nix \ negative-caching.nix \
binary-patching.nix \ binary-patching.nix \
timeout.nix timeout.builder.sh \
$(wildcard lang/*.nix) $(wildcard lang/*.exp) $(wildcard lang/*.exp.xml) $(wildcard lang/*.flags) \ $(wildcard lang/*.nix) $(wildcard lang/*.exp) $(wildcard lang/*.exp.xml) $(wildcard lang/*.flags) \
common.sh.in common.sh.in

2
tests/timeout.builder.sh Normal file
View File

@ -0,0 +1,2 @@
echo "\`timeout' builder entering an infinite loop"
while true ; do : ; done

8
tests/timeout.nix Normal file
View File

@ -0,0 +1,8 @@
with import ./config.nix;
mkDerivation {
name = "timeout";
builder = ./timeout.builder.sh;
PATH = "";
goodPath = path;
}

24
tests/timeout.sh Normal file
View File

@ -0,0 +1,24 @@
# Test the `--timeout' option.
source common.sh
drvPath=$($nixinstantiate timeout.nix)
test "$($nixstore -q --binding system "$drvPath")" = "$system"
echo "derivation is $drvPath"
failed=0
messages="`$nixstore -r --timeout 2 $drvPath 2>&1 || failed=1`"
if test $failed -ne 0; then
echo "error: \`nix-store' succeeded; should have timed out" >&2
exit 1
fi
if ! echo "$messages" | grep "timed out"; then
echo "error: \`nix-store' may have failed for reasons other than timeout" >&2
echo >&2
echo "output of \`nix-store' follows:" >&2
echo "$messages" >&2
exit 1
fi