about summary refs log tree commit diff
path: root/shared/n-acd/src/util/timer.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-03-26 23:25:23 +0100
committerMichael Biebl <biebl@debian.org>2019-03-26 23:25:23 +0100
commit9a6dcbf895f9da01768e64b73cec88c16157d91e (patch)
treea359958930d731e9f1b59344642e10754419fe84 /shared/n-acd/src/util/timer.c
parent964ae8cc391520440cf5aa13e2b9cc34850ea6c2 (diff)
New upstream version 1.16.0 upstream/1.16.0
Diffstat (limited to 'shared/n-acd/src/util/timer.c')
-rw-r--r--shared/n-acd/src/util/timer.c189
1 files changed, 189 insertions, 0 deletions
diff --git a/shared/n-acd/src/util/timer.c b/shared/n-acd/src/util/timer.c
new file mode 100644
index 00000000..29627af7
--- /dev/null
+++ b/shared/n-acd/src/util/timer.c
@@ -0,0 +1,189 @@
+/*
+ * 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 timeout 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);
+}