/*
* Timer Utility Library
*/
#include <assert.h>
#include <c-rbtree.h>
#include <c-stdaux.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/timerfd.h>
#include <time.h>
#include "timer.h"
int timer_init(Timer *timer) {
clockid_t clock = CLOCK_BOOTTIME;
int r;
r = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK);
if (r < 0 && errno == EINVAL) {
clock = CLOCK_MONOTONIC;
r = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK);
}
if (r < 0)
return -errno;
*timer = (Timer)TIMER_NULL(*timer);
timer->fd = r;
timer->clock = clock;
return 0;
}
void timer_deinit(Timer *timer) {
c_assert(c_rbtree_is_empty(&timer->tree));
if (timer->fd >= 0) {
close(timer->fd);
timer->fd = -1;
}
}
void timer_now(Timer *timer, uint64_t *nowp) {
struct timespec ts;
int r;
r = clock_gettime(timer->clock, &ts);
c_assert(r >= 0);
*nowp = ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec;
}
void timer_rearm(Timer *timer) {
uint64_t time;
Timeout *timeout;
int r;
/*
* A timeout value of 0 clears the timer, we should only set that if
* no timeout exists in the tree.
*/
timeout = c_rbnode_entry(c_rbtree_first(&timer->tree), Timeout, node);
c_assert(!timeout || timeout->timeout);
time = timeout ? timeout->timeout : 0;
if (time != timer->scheduled_timeout) {
r = timerfd_settime(timer->fd,
TFD_TIMER_ABSTIME,
&(struct itimerspec){
.it_value = {
.tv_sec = time / UINT64_C(1000000000),
.tv_nsec = time % UINT64_C(1000000000),
},
},
NULL);
c_assert(r >= 0);
timer->scheduled_timeout = time;
}
}
int timer_read(Timer *timer) {
uint64_t v;
int r;
r = read(timer->fd, &v, sizeof(v));
if (r < 0) {
if (errno == EAGAIN) {
/*
* No more pending events.
*/
return 0;
} else {
/*
* Something failed. We use CLOCK_BOOTTIME/MONOTONIC,
* so ECANCELED cannot happen. Hence, there is no
* error that we could gracefully handle. Fail hard
* and let the caller deal with it.
*/
return -errno;
}
} else if (r != sizeof(v) || v == 0) {
/*
* Kernel guarantees 8-byte reads, and only to return
* data if at least one timer triggered; fail hard if
* it suddenly starts doing weird shit.
*/
return -EIO;
}
return TIMER_E_TRIGGERED;
}
int timer_pop_timeout(Timer *timer, uint64_t until, Timeout **timeoutp) {
Timeout *timeout;
/*
* If the first timeout is scheduled before @until, then unlink
* it and return it. Otherwise, return NULL.
*/
timeout = c_rbnode_entry(c_rbtree_first(&timer->tree), Timeout, node);
if (timeout && timeout->timeout <= until) {
c_rbnode_unlink(&timeout->node);
timeout->timeout = 0;
*timeoutp = timeout;
} else {
*timeoutp = NULL;
}
return 0;
}
void timeout_schedule(Timeout *timeout, Timer *timer, uint64_t time) {
c_assert(time);
/*
* In case @timeout was already scheduled, remove it from the
* tree. If we are moving it to a new timer, rearm the old one.
*/
if (timeout->timer) {
c_rbnode_unlink(&timeout->node);
if (timeout->timer != timer)
timer_rearm(timeout->timer);
}
timeout->timer = timer;
timeout->timeout = time;
/*
* Now insert it back into the tree in the correct new position.
* We allow duplicates in the tree, so this insertion is open-coded.
*/
{
Timeout *other;
CRBNode **slot, *parent;
slot = &timer->tree.root;
parent = NULL;
while (*slot) {
other = c_rbnode_entry(*slot, Timeout, node);
parent = *slot;
if (timeout->timeout < other->timeout)
slot = &(*slot)->left;
else
slot = &(*slot)->right;
}
c_rbtree_add(&timer->tree, parent, slot, &timeout->node);
}
/*
* Rearm the timer as we updated the timeout tree.
*/
timer_rearm(timer);
}
void timeout_unschedule(Timeout *timeout) {
Timer *timer = timeout->timer;
if (!timer)
return;
c_rbnode_unlink(&timeout->node);
timeout->timeout = 0;
timeout->timer = NULL;
timer_rearm(timer);
}