From a6a7939e2c251e05d4ffee25f514e696303b1522 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 25 Nov 2018 17:45:11 +0100 Subject: shared: thread safe initialization of nm_utils_get_monotonic_timestamp*() nm_utils_get_monotonic_timestamp*() inherrently use static data. Let's initialize it in a thread safe manner. nm_utils_get_monotonic_timestamp*() are a fundamental utility function that should work correctly in all cases. Such a low level function should be thread safe. --- shared/nm-utils/nm-time-utils.c | 146 ++++++++++++++++++++++++++-------------- src/nm-logging.c | 4 +- 2 files changed, 98 insertions(+), 52 deletions(-) diff --git a/shared/nm-utils/nm-time-utils.c b/shared/nm-utils/nm-time-utils.c index 26b982246b..ae526c342e 100644 --- a/shared/nm-utils/nm-time-utils.c +++ b/shared/nm-utils/nm-time-utils.c @@ -24,41 +24,36 @@ /*****************************************************************************/ -static gint64 monotonic_timestamp_offset_sec; -static int monotonic_timestamp_clock_mode = 0; +typedef struct { + /* the offset to the native clock, in seconds. */ + gint64 offset_sec; + clockid_t clk_id; +} GlobalState; -static void -monotonic_timestamp_get (struct timespec *tp) +static const GlobalState *volatile p_global_state; + +static const GlobalState * +_t_init_global_state (void) { - int clock_mode = 0; - int err = 0; - - switch (monotonic_timestamp_clock_mode) { - case 0: - /* the clock is not yet initialized (first run) */ - err = clock_gettime (CLOCK_BOOTTIME, tp); - if (err == -1 && errno == EINVAL) { - clock_mode = 2; - err = clock_gettime (CLOCK_MONOTONIC, tp); - } else - clock_mode = 1; - break; - case 1: - /* default, return CLOCK_BOOTTIME */ - err = clock_gettime (CLOCK_BOOTTIME, tp); - break; - case 2: - /* fallback, return CLOCK_MONOTONIC. Kernels prior to 2.6.39 - * (released on 18 May, 2011) don't support CLOCK_BOOTTIME. */ - err = clock_gettime (CLOCK_MONOTONIC, tp); - break; - } + static GlobalState global_state = { }; + static gsize init_once = 0; + const GlobalState *p; + clockid_t clk_id; + struct timespec tp; + gint64 offset_sec; + int r; - g_assert (err == 0); (void)err; - g_assert (tp->tv_nsec >= 0 && tp->tv_nsec < NM_UTILS_NS_PER_SECOND); + clk_id = CLOCK_BOOTTIME; + r = clock_gettime (clk_id, &tp); + if (r == -1 && errno == EINVAL) { + clk_id = CLOCK_MONOTONIC; + r = clock_gettime (clk_id, &tp); + } - if (G_LIKELY (clock_mode == 0)) - return; + /* The only failure we tolerate is that CLOCK_BOOTTIME is not supported. + * Other than that, we rely on kernel to not fail on this. */ + g_assert (r == 0); + g_assert (tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NS_PER_SECOND); /* Calculate an offset for the time stamp. * @@ -73,12 +68,57 @@ monotonic_timestamp_get (struct timespec *tp) * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_s() * wraps (~68 years). **/ - monotonic_timestamp_offset_sec = (- ((gint64) tp->tv_sec)) + 1; - monotonic_timestamp_clock_mode = clock_mode; + offset_sec = (- ((gint64) tp.tv_sec)) + 1; + + if (!g_once_init_enter (&init_once)) { + /* there was a race. We expect the pointer to be fully initialized now. */ + p = g_atomic_pointer_get (&p_global_state); + g_assert (p); + return p; + } + + global_state.offset_sec = offset_sec; + global_state.clk_id = clk_id; + p = &global_state; + g_atomic_pointer_set (&p_global_state, p); + g_once_init_leave (&init_once, 1); - _nm_utils_monotonic_timestamp_initialized (tp, monotonic_timestamp_offset_sec, clock_mode == 1); + _nm_utils_monotonic_timestamp_initialized (&tp, + p->offset_sec, + p->clk_id == CLOCK_BOOTTIME); + + return p; } +#define _t_get_global_state() \ + ({ \ + const GlobalState *_p; \ + \ + _p = g_atomic_pointer_get (&p_global_state); \ + (G_LIKELY (_p) ? _p : _t_init_global_state ()); \ + }) + +#define _t_clock_gettime_eval(p, tp) \ + ({ \ + struct timespec *const _tp = (tp); \ + const GlobalState *const _p2 = (p); \ + int _r; \ + \ + nm_assert (_tp); \ + \ + _r = clock_gettime (_p2->clk_id, _tp); \ + \ + nm_assert (_r == 0); \ + nm_assert (_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NS_PER_SECOND); \ + \ + _p2; \ + }) + +#define _t_clock_gettime(tp) \ + _t_clock_gettime_eval (_t_get_global_state (), tp); + +/*****************************************************************************/ + /** * nm_utils_get_monotonic_timestamp_ns: * @@ -94,15 +134,16 @@ monotonic_timestamp_get (struct timespec *tp) gint64 nm_utils_get_monotonic_timestamp_ns (void) { - struct timespec tp = { 0 }; + const GlobalState *p; + struct timespec tp; - monotonic_timestamp_get (&tp); + p = _t_clock_gettime (&tp); /* Although the result will always be positive, we return a signed * integer, which makes it easier to calculate time differences (when * you want to subtract signed values). **/ - return (((gint64) tp.tv_sec) + monotonic_timestamp_offset_sec) * NM_UTILS_NS_PER_SECOND + + return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NS_PER_SECOND + tp.tv_nsec; } @@ -121,15 +162,16 @@ nm_utils_get_monotonic_timestamp_ns (void) gint64 nm_utils_get_monotonic_timestamp_us (void) { - struct timespec tp = { 0 }; + const GlobalState *p; + struct timespec tp; - monotonic_timestamp_get (&tp); + p = _t_clock_gettime (&tp); /* Although the result will always be positive, we return a signed * integer, which makes it easier to calculate time differences (when * you want to subtract signed values). **/ - return (((gint64) tp.tv_sec) + monotonic_timestamp_offset_sec) * ((gint64) G_USEC_PER_SEC) + + return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) G_USEC_PER_SEC) + (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/G_USEC_PER_SEC)); } @@ -148,15 +190,16 @@ nm_utils_get_monotonic_timestamp_us (void) gint64 nm_utils_get_monotonic_timestamp_ms (void) { - struct timespec tp = { 0 }; + const GlobalState *p; + struct timespec tp; - monotonic_timestamp_get (&tp); + p = _t_clock_gettime (&tp); /* Although the result will always be positive, we return a signed * integer, which makes it easier to calculate time differences (when * you want to subtract signed values). **/ - return (((gint64) tp.tv_sec) + monotonic_timestamp_offset_sec) * ((gint64) 1000) + + return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) 1000) + (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000)); } @@ -175,10 +218,12 @@ nm_utils_get_monotonic_timestamp_ms (void) gint32 nm_utils_get_monotonic_timestamp_s (void) { - struct timespec tp = { 0 }; + const GlobalState *p; + struct timespec tp; - monotonic_timestamp_get (&tp); - return (((gint64) tp.tv_sec) + monotonic_timestamp_offset_sec); + p = _t_clock_gettime (&tp); + + return (((gint64) tp.tv_sec) + p->offset_sec); } /** @@ -199,6 +244,7 @@ nm_utils_get_monotonic_timestamp_s (void) gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_per_tick) { + const GlobalState *p; gint64 offset; /* only support ns-per-tick being a multiple of 10. */ @@ -213,15 +259,15 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_ /* if the caller didn't yet ever fetch a monotonic-timestamp, he cannot pass any meaningful * value (because he has no idea what these timestamps would be). That would be a bug. */ - g_return_val_if_fail (monotonic_timestamp_clock_mode != 0, -1); + nm_assert (g_atomic_pointer_get (&p_global_state)); + + p = _t_get_global_state (); /* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */ - offset = monotonic_timestamp_offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick); + offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick); /* check for overflow. */ g_return_val_if_fail (offset > 0 || timestamp < G_MAXINT64 + offset, G_MAXINT64); return timestamp - offset; } - - diff --git a/src/nm-logging.c b/src/nm-logging.c index 328d164ba2..f64f03be48 100644 --- a/src/nm-logging.c +++ b/src/nm-logging.c @@ -748,8 +748,8 @@ _nm_log_impl (const char *file, void _nm_utils_monotonic_timestamp_initialized (const struct timespec *tp, - gint64 offset_sec, - gboolean is_boottime) + gint64 offset_sec, + gboolean is_boottime) { if (nm_logging_enabled (LOGL_DEBUG, LOGD_CORE)) { time_t now = time (NULL); -- cgit v1.2.1