/* * BIRD -- Timers * * (c) 2013--2017 Ondrej Zajicek <santiago@crfreenet.org> * (c) 2013--2017 CZ.NIC z.s.p.o. * * Can be freely distributed and used under the terms of the GNU GPL. */ /** * DOC: Timers * * Timers are resources which represent a wish of a module to call a function at * the specified time. The timer code does not guarantee exact timing, only that * a timer function will not be called before the requested time. * * In BIRD, time is represented by values of the &btime type which is signed * 64-bit integer interpreted as a relative number of microseconds since some * fixed time point in past. The current time can be obtained by current_time() * function with reasonable accuracy and is monotonic. There is also a current * 'wall-clock' real time obtainable by current_real_time() reported by OS. * * Each timer is described by a &timer structure containing a pointer to the * handler function (@hook), data private to this function (@data), time the * function should be called at (@expires, 0 for inactive timers), for the other * fields see |timer.h|. */ #include <stdio.h> #include <stdlib.h> #include <time.h> #include "nest/bird.h" #include "lib/heap.h" #include "lib/resource.h" #include "lib/timer.h" struct timeloop main_timeloop; #ifdef USE_PTHREADS #include <pthread.h> /* Data accessed and modified from proto/bfd/io.c */ pthread_key_t current_time_key; static inline struct timeloop * timeloop_current(void) { return pthread_getspecific(current_time_key); } static inline void timeloop_init_current(void) { pthread_key_create(¤t_time_key, NULL); pthread_setspecific(current_time_key, &main_timeloop); } void wakeup_kick_current(void); #else /* Just use main timelooop */ static inline struct timeloop * timeloop_current(void) { return &main_timeloop; } static inline void timeloop_init_current(void) { } #endif btime current_time(void) { return timeloop_current()->last_time; } btime current_real_time(void) { struct timeloop *loop = timeloop_current(); if (!loop->real_time) times_update_real_time(loop); return loop->real_time; } #define TIMER_LESS(a,b) ((a)->expires < (b)->expires) #define TIMER_SWAP(heap,a,b,t) (t = heap[a], heap[a] = heap[b], heap[b] = t, \ heap[a]->index = (a), heap[b]->index = (b)) static void tm_free(resource *r) { timer *t = (void *) r; tm_stop(t); } static void tm_dump(resource *r) { timer *t = (void *) r; debug("(code %p, data %p, ", t->hook, t->data); if (t->randomize) debug("rand %d, ", t->randomize); if (t->recurrent) debug("recur %d, ", t->recurrent); if (t->expires) debug("expires in %d ms)\n", (t->expires - current_time()) TO_MS); else debug("inactive)\n"); } static struct resclass tm_class = { "Timer", sizeof(timer), tm_free, tm_dump, NULL, NULL }; timer * tm_new(pool *p) { timer *t = ralloc(p, &tm_class); t->index = -1; return t; } void tm_set(timer *t, btime when) { struct timeloop *loop = timeloop_current(); uint tc = timers_count(loop); if (!t->expires) { t->index = ++tc; t->expires = when; BUFFER_PUSH(loop->timers) = t; HEAP_INSERT(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP); } else if (t->expires < when) { t->expires = when; HEAP_INCREASE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index); } else if (t->expires > when) { t->expires = when; HEAP_DECREASE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index); } #ifdef CONFIG_BFD /* Hack to notify BFD loops */ if ((loop != &main_timeloop) && (t->index == 1)) wakeup_kick_current(); #endif } void tm_start(timer *t, btime after) { tm_set(t, current_time() + MAX(after, 0)); } void tm_stop(timer *t) { if (!t->expires) return; struct timeloop *loop = timeloop_current(); uint tc = timers_count(loop); HEAP_DELETE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index); BUFFER_POP(loop->timers); t->index = -1; t->expires = 0; } void timers_init(struct timeloop *loop, pool *p) { times_init(loop); BUFFER_INIT(loop->timers, p, 4); BUFFER_PUSH(loop->timers) = NULL; } void io_log_event(void *hook, void *data); void timers_fire(struct timeloop *loop) { btime base_time; timer *t; times_update(loop); base_time = loop->last_time; while (t = timers_first(loop)) { if (t->expires > base_time) return; if (t->recurrent) { btime when = t->expires + t->recurrent; if (when <= loop->last_time) when = loop->last_time + t->recurrent; if (t->randomize) when += random() % (t->randomize + 1); tm_set(t, when); } else tm_stop(t); /* This is ugly hack, we want to log just timers executed from the main I/O loop */ if (loop == &main_timeloop) io_log_event(t->hook, t->data); t->hook(t); } } void timer_init(void) { timers_init(&main_timeloop, &root_pool); timeloop_init_current(); } /** * tm_parse_time - parse a date and time * @x: time string * * tm_parse_time() takes a textual representation of a date and time * (yyyy-mm-dd[ hh:mm:ss[.sss]]) and converts it to the corresponding value of * type &btime. */ btime tm_parse_time(const char *x) { struct tm tm = {}; int usec, n1, n2, n3, r; r = sscanf(x, "%d-%d-%d%n %d:%d:%d%n.%d%n", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &n1, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &n2, &usec, &n3); if ((r == 3) && !x[n1]) tm.tm_hour = tm.tm_min = tm.tm_sec = usec = 0; else if ((r == 6) && !x[n2]) usec = 0; else if ((r == 7) && !x[n3]) { /* Convert subsecond digits to proper precision */ int digits = n3 - n2 - 1; if ((usec < 0) || (usec > 999999) || (digits < 1) || (digits > 6)) return 0; while (digits++ < 6) usec *= 10; } else return 0; tm.tm_mon--; tm.tm_year -= 1900; s64 ts = mktime(&tm); if ((ts == (s64) (time_t) -1) || (ts < 0) || (ts > ((s64) 1 << 40))) return 0; return ts S + usec; } /** * tm_format_time - convert date and time to textual representation * @x: destination buffer of size %TM_DATETIME_BUFFER_SIZE * @fmt: specification of resulting textual representation of the time * @t: time * * This function formats the given relative time value @t to a textual * date/time representation (dd-mm-yyyy hh:mm:ss) in real time. */ void tm_format_time(char *x, struct timeformat *fmt, btime t) { btime dt = current_time() - t; btime rt = current_real_time() - dt; int v1 = !fmt->limit || (dt < fmt->limit); if (!tm_format_real_time(x, TM_DATETIME_BUFFER_SIZE, v1 ? fmt->fmt1 : fmt->fmt2, rt)) strcpy(x, "<error>"); } /* Replace %f in format string with usec scaled to requested precision */ static int strfusec(char *buf, int size, const char *fmt, uint usec) { char *str = buf; int parity = 0; while (*fmt) { if (!size) return 0; if ((fmt[0] == '%') && (!parity) && ((fmt[1] == 'f') || (fmt[1] >= '1') && (fmt[1] <= '6') && (fmt[2] == 'f'))) { int digits = (fmt[1] == 'f') ? 6 : (fmt[1] - '0'); uint d = digits, u = usec; /* Convert microseconds to requested precision */ while (d++ < 6) u /= 10; int num = bsnprintf(str, size, "%0*u", digits, u); if (num < 0) return 0; fmt += (fmt[1] == 'f') ? 2 : 3; ADVANCE(str, size, num); } else { /* Handle '%%' expression */ parity = (*fmt == '%') ? !parity : 0; *str++ = *fmt++; size--; } } if (!size) return 0; *str = 0; return str - buf; } int tm_format_real_time(char *x, size_t max, const char *fmt, btime t) { s64 t1 = t TO_S; s64 t2 = t - t1 S; time_t ts = t1; struct tm tm; if (!localtime_r(&ts, &tm)) return 0; size_t tbuf_size = MIN(max, 4096); byte *tbuf = alloca(tbuf_size); if (!strfusec(tbuf, tbuf_size, fmt, t2)) return 0; if (!strftime(x, max, tbuf, &tm)) return 0; return 1; }