Hello,

The attached patch adds support for ‘build-timeout’ and ‘--timeout’,
which ‘hydra_build.pl’ has been hoping for for some time apparently.
;-)

There’s a FIXME in ‘HookInstance’ because apparently ‘maxSilentTime’ is
passed as part of the build hook protocol (?), so we should do the same
with ‘buildTimeout’.

OK to apply?

Thanks,
Ludo’.

>From 4154b30ac40d2dde4112a332cc12b4f1ac6f236c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= <[email protected]>
Date: Wed, 29 Jun 2011 16:16:39 +0200
Subject: [PATCH] Add support for the `build-timeout' and `--timeout' options.

---
 doc/manual/conf-file.xml      |   17 +++++++++++++++++
 doc/manual/opt-common-syn.xml |    4 ++++
 doc/manual/opt-common.xml     |   10 ++++++++++
 doc/manual/release-notes.xml  |   10 ++++++++++
 src/libexpr/value-to-xml.cc   |    2 +-
 src/libmain/shared.cc         |    3 +++
 src/libstore/build.cc         |   25 ++++++++++++++++++++++---
 src/libstore/globals.cc       |    1 +
 src/libstore/globals.hh       |    4 ++++
 tests/Makefile.am             |    3 ++-
 tests/timeout.builder.sh      |    2 ++
 tests/timeout.nix             |    8 ++++++++
 tests/timeout.sh              |   24 ++++++++++++++++++++++++
 13 files changed, 108 insertions(+), 5 deletions(-)
 create mode 100644 tests/timeout.builder.sh
 create mode 100644 tests/timeout.nix
 create mode 100644 tests/timeout.sh

diff --git a/doc/manual/conf-file.xml b/doc/manual/conf-file.xml
index cb47b99..44a7ac3 100644
--- a/doc/manual/conf-file.xml
+++ b/doc/manual/conf-file.xml
@@ -134,6 +134,23 @@ env-keep-derivations = false
 
     </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>
 
 
diff --git a/doc/manual/opt-common-syn.xml b/doc/manual/opt-common-syn.xml
index da60288..23bd36d 100644
--- a/doc/manual/opt-common-syn.xml
+++ b/doc/manual/opt-common-syn.xml
@@ -21,6 +21,10 @@
   <arg><option>--max-silent-time</option></arg>
   <replaceable>number</replaceable>
 </arg>
+<arg>
+  <arg><option>--timeout</option></arg>
+  <replaceable>number</replaceable>
+</arg>
 <arg><option>--keep-going</option></arg>
 <arg><option>-k</option></arg>
 <arg><option>--keep-failed</option></arg>
diff --git a/doc/manual/opt-common.xml b/doc/manual/opt-common.xml
index 2d67162..e86e4b4 100644
--- a/doc/manual/opt-common.xml
+++ b/doc/manual/opt-common.xml
@@ -132,6 +132,16 @@
 
 </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>
   <term><option>-k</option></term>
 
diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml
index 1e579a3..0c29cae 100644
--- a/doc/manual/release-notes.xml
+++ b/doc/manual/release-notes.xml
@@ -22,6 +22,16 @@
     option.</para>
   </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>
 
 </section>
diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc
index bc63f77..44950f7 100644
--- a/src/libexpr/value-to-xml.cc
+++ b/src/libexpr/value-to-xml.cc
@@ -105,8 +105,8 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
                 XMLOpenElement _(doc, "derivation", xmlAttrs);
 
                 if (drvPath != "" && drvsSeen.find(drvPath) == drvsSeen.end()) {
-                    drvsSeen.insert(drvPath);
                     showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen);
+                    drvsSeen.insert(drvPath);
                 } else
                     doc.writeEmptyElement("repeated");
             }
diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc
index 3110c94..43ec4bc 100644
--- a/src/libmain/shared.cc
+++ b/src/libmain/shared.cc
@@ -142,6 +142,7 @@ static void initAndRun(int argc, char * * argv)
     maxBuildJobs = queryIntSetting("build-max-jobs", 1);
     buildCores = queryIntSetting("build-cores", 1);
     maxSilentTime = queryIntSetting("build-max-silent-time", 0);
+    buildTimeout = queryIntSetting("build-timeout", 0);
 
     /* Catch SIGINT. */
     struct sigaction act;
@@ -237,6 +238,8 @@ static void initAndRun(int argc, char * * argv)
             readOnlyMode = true;
         else if (arg == "--max-silent-time")
             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")
             useBuildHook = false;
         else if (arg == "--show-trace")
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 83bd675..06e06a5 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -681,7 +681,8 @@ HookInstance::HookInstance()
             builderOut.readSide.close();
             if (dup2(builderOut.writeSide, 4) == -1)
                 throw SysError("dupping builder's stdout/stderr");
-            
+
+	    /* FIXME: Pass `buildTimeout' to the hook.  */
             execl(buildHook.c_str(), buildHook.c_str(), thisSystem.c_str(),
                 (format("%1%") % maxSilentTime).str().c_str(),
                 (format("%1%") % printBuildTrace).str().c_str(),
@@ -2666,7 +2667,13 @@ void Worker::waitForInput()
     struct timeval timeout;
     timeout.tv_usec = 0;
     time_t before = time(0);
-        
+
+    /* If a global timeout has been set, sleep until it's done.  */
+    if (buildTimeout != 0) {
+	useTimeout = true;
+	timeout.tv_sec = buildTimeout;
+    }
+
     /* If we're monitoring for silence on stdout/stderr, sleep until
        the first deadline for any child. */
     if (maxSilentTime != 0) {
@@ -2678,8 +2685,11 @@ void Worker::waitForInput()
             }
         }
         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;
-            timeout.tv_sec = std::max((time_t) 0, oldest + maxSilentTime - before);
             printMsg(lvlVomit, format("sleeping %1% seconds") % timeout.tv_sec);
         }
     }
@@ -2765,6 +2775,15 @@ void Worker::waitForInput()
                 % goal->getName() % maxSilentTime);
             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) {
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index 7069d10..2e9dc88 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -26,6 +26,7 @@ unsigned int buildCores = 1;
 bool readOnlyMode = false;
 string thisSystem = "unset";
 time_t maxSilentTime = 0;
+time_t buildTimeout = 0;
 Paths substituters;
 bool useBuildHook = true;
 bool printBuildTrace = false;
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index a74a741..231c1f8 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -72,6 +72,10 @@ extern string thisSystem;
    infinity. */
 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
    store path without building, e.g., by downloading it or copying it
    from a CD. */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index fb4a228..d383bce 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -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 \
   gc-runtime.sh install-package.sh check-refs.sh filter-source.sh \
   remote-store.sh export.sh export-graph.sh negative-caching.sh \
-  binary-patching.sh
+  binary-patching.sh timeout.sh
 
 XFAIL_TESTS =
 
@@ -33,5 +33,6 @@ EXTRA_DIST = $(TESTS) \
   export-graph.nix \
   negative-caching.nix \
   binary-patching.nix \
+  timeout.nix timeout.builder.sh \
   $(wildcard lang/*.nix) $(wildcard lang/*.exp) $(wildcard lang/*.exp.xml) $(wildcard lang/*.flags) \
   common.sh.in
diff --git a/tests/timeout.builder.sh b/tests/timeout.builder.sh
new file mode 100644
index 0000000..3e6e1fc
--- /dev/null
+++ b/tests/timeout.builder.sh
@@ -0,0 +1,2 @@
+echo "\`timeout' builder entering an infinite loop"
+while true ; do : ; done
diff --git a/tests/timeout.nix b/tests/timeout.nix
new file mode 100644
index 0000000..dec2588
--- /dev/null
+++ b/tests/timeout.nix
@@ -0,0 +1,8 @@
+with import ./config.nix;
+
+mkDerivation {
+  name = "timeout";
+  builder = ./timeout.builder.sh;
+  PATH = "";
+  goodPath = path;
+}
diff --git a/tests/timeout.sh b/tests/timeout.sh
new file mode 100644
index 0000000..f27739f
--- /dev/null
+++ b/tests/timeout.sh
@@ -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
-- 
1.7.4.1

_______________________________________________
nix-dev mailing list
[email protected]
https://mail.cs.uu.nl/mailman/listinfo/nix-dev

Reply via email to