summaryrefslogtreecommitdiffstats
path: root/lib/qtime.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/qtime.c')
-rw-r--r--lib/qtime.c253
1 files changed, 206 insertions, 47 deletions
diff --git a/lib/qtime.c b/lib/qtime.c
index 42b903da..d1283198 100644
--- a/lib/qtime.c
+++ b/lib/qtime.c
@@ -18,11 +18,11 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
#include <sys/times.h>
#include <errno.h>
-#include "zassert.h"
#include "qtime.h"
/*==============================================================================
@@ -69,17 +69,40 @@
* (It appears that 60, 100, 250 and 1,000 ticks/sec. are popular options.)
*
* If sizeof(clock_t) > 4, it is assumed large enough never to wrap around.
+ * (But seems unlikely that such a system would not support CLOCK_MONOTONIC !)
*
* When clock_t is a 32-bit integer must be at least ready for wrap around.
- * There are two cases:
+ * We take the clock_t signed values and widen to 64-bit signed, so we have
+ * the current sample (this) and the previous one (last), and two cases to
+ * consider:
*
- * * +ve wrap around. new < old value, and new >= 0
+ * * +ve wrap around -- so value is 31-bit unsigned, and wraps from large
+ * +ve value to small +ve value.
*
- * step = (INT32_MAX - old + 1) + new
+ * step = this - last will be -ve
*
- * * -ve wrap around. new < old value, and new < 0 (and old > 0)
+ * 'last' will be some value ((INT32_MAX + 1) - x), and 'this' will be some
+ * (relatively) small value y. The step is x + y, we have:
*
- * step = (INT32_MAX - old + 1) - (INT32_MIN - new)
+ * step = y - ((INT32_MAX + 1) - x) = (x + y) - (INT32_MAX + 1)
+ *
+ * so we correct by adding (INT32_MAX + 1).
+ *
+ * * -ve wrap around -- so value is 32-bit signed, and wraps from a large
+ * +ve value to a very -ve value.
+ *
+ * step = this - last will be -ve
+ *
+ * 'last will' be some value (INT32_MAX + 1) - x, and 'this' will be some
+ * value (y - (INT32_MAX + 1)). The step is x + y, we have:
+ *
+ * step = (y - (INT32_MAX + 1)) - ((INT32_MAX + 1) - x)
+ * = (x + y) - 2 * (INT32_MAX + 1)
+ *
+ * so we correct by adding (INT32_MAX + 1).
+ *
+ * In both cases the wrap around gives an apparently -ve 'step', and that is
+ * corrected by adding (INT32_MAX + 1) until it goes +ve.
*
* In any event, a step > 24 hours is taken to means that something has gone
* very, very badly wrong.
@@ -97,23 +120,24 @@ CONFIRM((sizeof(clock_t) >= 4) && (sizeof(clock_t) <= 8)) ;
#ifdef GNU_LINUX
#define TIMES_TAKES_NULL 1
#else
-#undef TIMES_TAKES_NULL
+#define TIMES_TAKES_NULL 0
#endif
-static uint64_t monotonic = 0 ; /* monotonic clock in _SC_CLK_TCK's */
-static int64_t last_times_sample = 0 ; /* last value returned by times() */
+static int64_t monotonic = 0 ; /* monotonic clock in _SC_CLK_TCK's */
+static int64_t last_times_sample = 0 ; /* last value returned by times() */
-static uint64_t step_limit = 0 ; /* for sanity check */
+static int64_t step_limit = 0 ; /* for sanity check */
-static int64_t times_clk_tcks = 0 ; /* sysconf(_SC_CLK_TCK) */
-static qtime_t times_scale_q = 0 ; /* 10**9 / times_clk_tcks */
-static qtime_t times_scale_r = 0 ; /* 10**9 % times_clk_tcks */
+static int64_t times_clk_tcks = 0 ; /* sysconf(_SC_CLK_TCK) */
+static qtime_t times_scale_q = 0 ; /* 10**9 / times_clk_tcks */
+static qtime_t times_scale_r = 0 ; /* 10**9 % times_clk_tcks */
qtime_mono_t
qt_craft_monotonic(void) {
- struct tms dummy ;
- int64_t this_times_sample ;
- uint64_t step ;
+#if !TIMES_TAKES_NULL
+ struct tms dummy[1] ;
+#endif
+ clock_t this_times_sample ;
/* Set up times_scale_q & times_scale_q if not yet done. */
if (times_clk_tcks == 0) /* Is zero until it's initialized */
@@ -130,7 +154,7 @@ qt_craft_monotonic(void) {
times_scale_q = qr.quot ;
times_scale_r = qr.rem ;
- step_limit = (uint64_t)24 * 60 * 60 * times_clk_tcks ;
+ step_limit = (int64_t)24 * 60 * 60 * times_clk_tcks ;
} ;
/* No errors are defined for times(), but a return of -1 is defined */
@@ -139,51 +163,54 @@ qt_craft_monotonic(void) {
/* The following deals carefully with this -- cannot afford for the */
/* clock either to jump or to get stuck ! */
-#ifdef TIMES_TAKES_NULL
- this_times_sample = times(NULL) ; /* assume this saves effort ! */
+#if TIMES_TAKES_NULL
+# define TIMES_ARG NULL
#else
- this_times_sample = times(&dummy) ;
+# define TIMES_ARG dummy
#endif
+ this_times_sample = times(TIMES_ARG) ;
if (this_times_sample == -1) /* deal with theoretical error */
{
errno = 0 ;
- this_times_sample = times(&dummy) ;
+ this_times_sample = times(TIMES_ARG) ;
if (errno != 0)
zabort_errno("times() failed") ;
} ;
+#undef TIMES_ARG
- /* Calculate the step and verify sensible. */
- /* */
- /* Watch out for huge jumps and/or time going backwards. */
- /* For 32-bit clock_t, look out for wrap-around. */
-
- if ((sizeof(clock_t) > 4) || (this_times_sample > last_times_sample))
- /* time going backwards will appear as HUGE step forwards. */
- step = (uint64_t)(this_times_sample - last_times_sample) ;
+ /* If clock_t is large enough, treat as monotonic (!).
+ *
+ * Otherwise calculate the difference between this sample and the
+ * previous one -- the step.
+ *
+ * We do the sum in signed 64 bits, and the samples are signed 64 bits.
+ */
+ if (sizeof(clock_t) > 4)
+ monotonic = this_times_sample ;
else
{
- if (this_times_sample > 0)
- /* both samples +ve => +ve wrap around. */
- step = (uint64_t)( ((int64_t)INT32_MAX - last_times_sample + 1)
- + this_times_sample ) ;
- else
- /* this sample -ve and last sample +ve => -ve wrap round */
- /* this sample -ve and last sample -ve => time gone backwards */
- /* (which appears as a HUGE step forwards). */
- step = (uint64_t)( ((int64_t)INT32_MAX - last_times_sample + 1)
- - ((int64_t)INT32_MIN - this_times_sample) ) ;
- } ;
+ int64_t step ;
- /* TODO: better error messaging for large clock jumps. */
- if (step > step_limit)
- zabort("Sudden large monotonic clock jump") ;
+ step = this_times_sample - last_times_sample ;
- /* Advance the monotonic clock in sysconf(_SC_CLK_TCK) units. */
- monotonic += step ;
+ while (step < 0)
+ {
+ /* If times() wraps unsigned, then result needs INT32_MAX + 1
+ * adding to it to get to +ve result.
+ *
+ * If times() wraps signed, then result needs INT32_MAX + 1 adding
+ * to it *twice*.
+ */
+ step += (uint64_t)INT32_MAX + 1 ;
+ } ;
- /* Remember what we got, for next time. */
- last_times_sample = this_times_sample ;
+ if (step > step_limit)
+ zabort("Sudden large monotonic clock jump") ;
+
+ monotonic += step ;
+ last_times_sample = this_times_sample ;
+ } ;
/* Scale to qtime_t units. */
if (times_scale_r == 0)
@@ -203,3 +230,135 @@ qt_craft_mono_secs(void)
return monotonic / times_clk_tcks ;
} ;
+
+/*==============================================================================
+ * A simple minded random number generator.
+ *
+ * Designed to be reasonably unpredictable... particularly the ms bits !
+ */
+
+static inline uint32_t
+qt_rand(uint64_t q, uint64_t s)
+{
+ /* Takes q ^ s and reduces to 32 bits by xoring ms and ls halves
+ * then uses Knuth recommended linear congruent to randomise that, so that
+ * most of the original bits affect the result.
+ *
+ * Note that linear congruent tends to be "more random" in the ms bits.
+ */
+ q ^= s ;
+ q = (q ^ (q >> 32)) & 0xFFFFFFFF ;
+ return ((q * 2650845021) + 5) & 0xFFFFFFFF ;
+} ;
+
+extern uint32_t
+qt_random(uint32_t seed)
+{
+ uint32_t x, y ;
+ uint64_t t ;
+
+ t = qt_get_realtime() ;
+
+ /* Set x by munging the time, the address of x, the current contents of x,
+ * and the "seed". (Munge the seed a bit for good measure.)
+ */
+ x = qt_rand(t ^ (uint64_t)x ^ (uint64_t)&x, seed ^ 3141592653) ;
+ /* munge the address and the contents with the seed */
+
+ /* Set y by munging the time, the address of y, the current contents of y,
+ * and the "seed". (Munge the seed a bit for good measure.)
+ */
+ y = qt_rand(t ^ (uint64_t)y ^ (uint64_t)&y, seed ^ 3562951413) ;
+ /* munge the current real time with the seed */
+
+ /* Return x and y munged together.
+ *
+ * Note that we swap the halves of y before munging, in order to spread
+ * the "more random" part of y down to the ls end of the result.
+ */
+ return x ^ ((y >> 16) & 0xFFFF) ^ ((y & 0xFFFF) << 16) ;
+} ;
+
+/*==============================================================================
+ * Tracking the local timezone, so can:
+ *
+ * a) rapidly convert clock_gettime(CLOCK_REALTIME, ...) times
+ *
+ * b) do that thread-safe
+ *
+ * c) do that async-signal-safe
+ *
+ * Assumptions:
+ *
+ * a) that timezones are on at most 5 minute boundaries (15 probably!)
+ *
+ * b) that DST changes are at least 60 days apart
+ *
+ * c) that DST changes occur on times which are on 5 minute boundaries
+ * (60 probably -- but this means that the DST change is on a 5 minute
+ * bounderay in local and epoch times !)
+ *
+ * Sets up and maintains a table containing 8 entries:
+ *
+ * [-3] previous - 2
+ * [-2] previous - 1
+ * [-1] previous -- previous 0-7 days
+ * [ 0] current -- current 0-7 days
+ * [+1] next -- next 0-7 days
+ * [+2] next + 1
+ * [+3] next + 2
+ * [ X] sentinal
+ *
+ * These are configured before any threads or anything else very much runs, so
+ * they are essentially static. There is a "current index", which is set to
+ * '0' to start with.
+ *
+ * Each entry comprises:
+ *
+ * * start time -- entry is valid for epoch times >= start
+ * * end time -- entry is valid for epoch times < end
+ * * offset -- add to epoch time to get local
+ *
+ * When set up the current timezone initially starts on the nearest 5 minute
+ * boundary in the past, and covers up to 7 days into the future, unless the
+ * timezone changes in that time. The timezones on either side are set
+ * similarly.
+ *
+ * At most one of these timezones may be a short one -- so this covers at least
+ * 14 days into the past and 21 into the future, and as time advances, upto
+ * 21 days into the past and down to 14 days into the future.
+ *
+ * Maximum range is 56 days -- which is within the assumed 60 days between
+ * DST changes.
+ *
+ * When time advances past the current entry the next, next + 1 and + 2 cover
+ * at least 14 days (21 if none are short entries).
+ *
+ * Every now and then (say every 5 minutes) a background process can check the
+ * current time. If that is no longer in the current entry, needs to update
+ * the table. Assuming time is moving forward: sets sentinal to be the next
+ * 0-7 days following the current last entry, and updates the "current index".
+ *
+ * BIG ASSUMPTION: that the "current index" value is written atomically, wrt
+ * to threads as well as signals.
+ *
+ * It doesn't matter if a thread or signal action code picks
+ * up an out of date "current index" value, because all the
+ * entries for the old state are still valid.
+ *
+ * No entry is changed while it is covered by the current
+ * index -3..+3.
+ *
+ * This works fine, UNLESS the clock_gettime(CLOCK_REALTIME, ...) changes
+ * dramatically -- as might happen if the operator adjusts the system clock a
+ * long way !
+ *
+ * To cope with this, a spare set of 8 entries are kept, and a new table can
+ * be built (under mutex). The worst that happens is that threads may be
+ * blocked waiting for the table to be updated.
+ *
+ * If the table is found to be out of date when a signal is bringing the
+ * system down, then the times logged will just have to use either the first
+ * or the last entry, and have done with it.
+ */
+