diff options
Diffstat (limited to 'lib/timer.c')
-rw-r--r-- | lib/timer.c | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/lib/timer.c b/lib/timer.c new file mode 100644 index 00000000..ed731d26 --- /dev/null +++ b/lib/timer.c @@ -0,0 +1,378 @@ +/* + * 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(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); + + tm_format_real_time(x, v1 ? fmt->fmt1 : fmt->fmt2, rt); +} + +/* 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; +} + +void +tm_format_real_time(char *x, 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)) + goto err; + + byte tbuf[TM_DATETIME_BUFFER_SIZE]; + if (!strfusec(tbuf, TM_DATETIME_BUFFER_SIZE, fmt, t2)) + goto err; + + if (!strftime(x, TM_DATETIME_BUFFER_SIZE, tbuf, &tm)) + goto err; + + return; + +err: + strcpy(x, "<error>"); +} |