timeout: support sub-second timeouts
authorPádraig Brady <P@draigBrady.com>
Mon, 18 Jul 2011 09:49:17 +0000 (10:49 +0100)
committerPádraig Brady <P@draigBrady.com>
Mon, 25 Jul 2011 11:00:26 +0000 (12:00 +0100)
* src/timeout.c (settimeout): A new function to convert
from a floating point duration and call alarm() or
timer_settime() if that's available.
(parse_duration): Return a double rather than unsigned int.
(usage): Mention floating point is supported.
(main): Pass the double to settimeout() rather than
calling alarm() directly with the parsed int.
(cleanup): Likewise.
* doc/coreutils.texi (timeout invocation): Say floating point timeouts
now supported, and mention the caveat with resolution.
* bootstrap.conf: Include the timer-time gnulib module.
* tests/misc/timeout-parameters: Add a test with nanoseconds.
* NEWS: Mention the improvement.

NEWS
bootstrap.conf
doc/coreutils.texi
src/Makefile.am
src/timeout.c
tests/misc/timeout-parameters

diff --git a/NEWS b/NEWS
index 0720719..fb444f6 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -59,6 +59,8 @@ GNU coreutils NEWS                                    -*- outline -*-
 
   stat -f now recognizes the GPFS, MQUEUE and PSTOREFS file system types.
 
+  timeout now supports sub-second timeouts.
+
 ** Build-related
 
   Changes inherited from gnulib address a build failure on HP-UX 11.11
index d91bee0..937f7f3 100644 (file)
@@ -212,6 +212,7 @@ gnulib_modules="
   sys_stat
   sys_wait
   termios
+  timer-time
   timespec
   tzset
   uname
index 424446c..b406a3c 100644 (file)
@@ -15694,7 +15694,7 @@ or a number. Also see @xref{Signal specifications}.
 @end table
 
 @cindex time units
-@var{duration} is an integer followed by an optional unit:
+@var{duration} is a floating point number followed by an optional unit:
 @display
 @samp{s} for seconds (the default)
 @samp{m} for minutes
@@ -15702,6 +15702,8 @@ or a number. Also see @xref{Signal specifications}.
 @samp{d} for days
 @end display
 A duration of 0 disables the associated timeout.
+Note that the actual timeout duration is dependent on system conditions,
+which should be especially considered when specifying sub-second timeouts.
 
 @cindex exit status of @command{timeout}
 Exit status:
index ef0e7a4..b0b7eb5 100644 (file)
@@ -329,6 +329,7 @@ date_LDADD += $(LIB_CLOCK_GETTIME)
 ginstall_LDADD += $(LIB_CLOCK_GETTIME)
 ls_LDADD += $(LIB_CLOCK_GETTIME)
 pr_LDADD += $(LIB_CLOCK_GETTIME)
+timeout_LDADD += $(LIB_TIMER_TIME)
 touch_LDADD += $(LIB_CLOCK_GETTIME)
 
 # for gethrxtime
index ccb4f85..6a37508 100644 (file)
@@ -78,7 +78,7 @@ static int timed_out;
 static int term_signal = SIGTERM;  /* same default as kill command.  */
 static int monitored_pid;
 static int sigs_to_ignore[NSIG];   /* so monitor can ignore sigs it resends.  */
-static unsigned long kill_after;
+static double kill_after;
 static bool foreground;            /* whether to use another program group.  */
 
 /* for long options with no corresponding short option, use enum */
@@ -97,6 +97,50 @@ static struct option const long_options[] =
   {NULL, 0, NULL, 0}
 };
 
+/* Start the timeout after which we'll receive a SIGALRM.
+   Round DURATION up to the next representable value.
+   Treat out-of-range values as if they were maximal,
+   as that's more useful in practice than reporting an error.
+   '0' means don't timeout.  */
+static void
+settimeout (double duration)
+{
+/* timer_settime() provides potentially nanosecond resolution.
+   setitimer() is more portable (to Darwin for example),
+   but only provides microsecond resolution and thus is
+   a little more awkward to use with timespecs, as well as being
+   deprecated by POSIX.  Instead we fallback to single second
+   resolution provided by alarm().  */
+
+#if HAVE_TIMER_SETTIME
+  struct timespec ts = dtotimespec (duration);
+  struct itimerspec its = { {0, 0}, ts };
+  timer_t timerid;
+  if (timer_create (CLOCK_REALTIME, NULL, &timerid) == 0)
+    {
+      if (timer_settime (timerid, 0, &its, NULL) == 0)
+        return;
+      else
+        {
+          error (0, errno, _("warning: timer_settime"));
+          timer_delete (timerid);
+        }
+    }
+  else
+    error (0, errno, _("warning: timer_create"));
+#endif
+
+  unsigned int timeint;
+  if (UINT_MAX <= duration)
+    timeint = UINT_MAX;
+  else
+    {
+      unsigned int duration_floor = duration;
+      timeint = duration_floor + (duration_floor < duration);
+    }
+  alarm (timeint);
+}
+
 /* send sig to group but not ourselves.
  * FIXME: Is there a better way to achieve this?  */
 static int
@@ -125,7 +169,7 @@ cleanup (int sig)
         {
           /* Start a new timeout after which we'll send SIGKILL.  */
           term_signal = SIGKILL;
-          alarm (kill_after);
+          settimeout (kill_after);
           kill_after = 0; /* Don't let later signals reset kill alarm.  */
         }
 
@@ -182,7 +226,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
 
       fputs (_("\n\
-DURATION is an integer with an optional suffix:\n\
+DURATION is a floating point number with an optional suffix:\n\
 `s' for seconds (the default), `m' for minutes, `h' for hours \
 or `d' for days.\n"), stdout);
 
@@ -232,7 +276,7 @@ apply_time_suffix (double *x, char suffix_char)
   return true;
 }
 
-static unsigned int
+static double
 parse_duration (const char* str)
 {
   double duration;
@@ -250,19 +294,7 @@ parse_duration (const char* str)
       usage (EXIT_CANCELED);
     }
 
-  /* Return the requested duration, rounded up to the next representable value.
-     Treat out-of-range values as if they were maximal,
-     as that's more useful in practice than reporting an error.
-
-     FIXME: Use dtotimespec + setitimer if setitimer is available,
-     as that has higher resolution.  */
-  if (UINT_MAX <= duration)
-    return UINT_MAX;
-  else
-    {
-      unsigned int duration_floor = duration;
-      return duration_floor + (duration_floor < duration);
-    }
+  return duration;
 }
 
 static void
@@ -285,7 +317,7 @@ install_signal_handlers (int sigterm)
 int
 main (int argc, char **argv)
 {
-  unsigned long timeout;
+  double timeout;
   char signame[SIG2STR_MAX];
   int c;
 
@@ -373,7 +405,7 @@ main (int argc, char **argv)
       pid_t wait_result;
       int status;
 
-      alarm (timeout);
+      settimeout (timeout);
 
       while ((wait_result = waitpid (monitored_pid, &status, 0)) < 0
              && errno == EINTR)
index 56804a8..e34a728 100755 (executable)
@@ -55,6 +55,9 @@ test $? = 0 || fail=1
 timeout 2.34e+5d sleep 0
 test $? = 0 || fail=1
 
+# nanoseconds potentially supported
+timeout .999999999 sleep 0 || fail=1
+
 # invalid signal spec
 timeout --signal=invalid 1 sleep 0
 test $? = 125 || fail=1