190 lines
5.4 KiB
C
190 lines
5.4 KiB
C
/*
|
|
* Timer Utility Library
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <c-rbtree.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) {
|
|
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);
|
|
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 sholud only set that if
|
|
* no timout exists in the tree.
|
|
*/
|
|
|
|
timeout = c_rbnode_entry(c_rbtree_first(&timer->tree), Timeout, node);
|
|
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);
|
|
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) {
|
|
|
|
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);
|
|
}
|