summaryrefslogtreecommitdiffstats
path: root/src/knot/common/evsched.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/common/evsched.c')
-rw-r--r--src/knot/common/evsched.c268
1 files changed, 268 insertions, 0 deletions
diff --git a/src/knot/common/evsched.c b/src/knot/common/evsched.c
new file mode 100644
index 0000000..0d65c6a
--- /dev/null
+++ b/src/knot/common/evsched.c
@@ -0,0 +1,268 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include "libknot/libknot.h"
+#include "knot/server/dthreads.h"
+#include "knot/common/evsched.h"
+
+/*! \brief Some implementations of timercmp >= are broken, this is for compat.*/
+static inline int timercmp_ge(struct timeval *a, struct timeval *b) {
+ return !timercmp(a, b, <);
+}
+
+static int compare_event_heap_nodes(void *e1, void *e2)
+{
+ if (timercmp(&((event_t *)e1)->tv, &((event_t *)e2)->tv, <)) return -1;
+ if (timercmp(&((event_t *)e1)->tv, &((event_t *)e2)->tv, >)) return 1;
+ return 0;
+}
+
+/*!
+ * \brief Get time T (now) + dt milliseconds.
+ */
+static struct timeval timeval_in(uint32_t dt)
+{
+ struct timeval tv = { 0 };
+ gettimeofday(&tv, NULL);
+
+ /* Add number of seconds. */
+ tv.tv_sec += dt / 1000;
+
+ /* Add the number of microseconds. */
+ tv.tv_usec += (dt % 1000) * 1000;
+
+ /* Check for overflow. */
+ while (tv.tv_usec > 999999) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1 * 1000 * 1000;
+ }
+
+ return tv;
+}
+
+/*! \brief Event scheduler loop. */
+static int evsched_run(dthread_t *thread)
+{
+ evsched_t *sched = (evsched_t*)thread->data;
+ if (sched == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Run event loop. */
+ pthread_mutex_lock(&sched->heap_lock);
+ while (!dt_is_cancelled(thread)) {
+ if (!!EMPTY_HEAP(&sched->heap) || sched->paused) {
+ pthread_cond_wait(&sched->notify, &sched->heap_lock);
+ continue;
+ }
+
+ /* Get current time. */
+ struct timeval dt;
+ gettimeofday(&dt, 0);
+
+ /* Get next event. */
+ event_t *ev = *((event_t**)HHEAD(&sched->heap));
+ assert(ev != NULL);
+
+ if (timercmp_ge(&dt, &ev->tv)) {
+ heap_delmin(&sched->heap);
+ ev->cb(ev);
+ } else {
+ /* Wait for next event or interrupt. Unlock calendar. */
+ struct timespec ts;
+ ts.tv_sec = ev->tv.tv_sec;
+ ts.tv_nsec = ev->tv.tv_usec * 1000L;
+ pthread_cond_timedwait(&sched->notify, &sched->heap_lock, &ts);
+ }
+ }
+ pthread_mutex_unlock(&sched->heap_lock);
+
+ return KNOT_EOK;
+}
+
+int evsched_init(evsched_t *sched, void *ctx)
+{
+ memset(sched, 0, sizeof(evsched_t));
+ sched->ctx = ctx;
+
+ /* Initialize event calendar. */
+ pthread_mutex_init(&sched->heap_lock, 0);
+ pthread_cond_init(&sched->notify, 0);
+ heap_init(&sched->heap, compare_event_heap_nodes, 0);
+
+ sched->thread = dt_create(1, evsched_run, NULL, sched);
+
+ if (sched->thread == NULL) {
+ evsched_deinit(sched);
+ return KNOT_ENOMEM;
+ }
+
+ return KNOT_EOK;
+}
+
+void evsched_deinit(evsched_t *sched)
+{
+ if (sched == NULL) {
+ return;
+ }
+
+ /* Deinitialize event calendar. */
+ pthread_mutex_destroy(&sched->heap_lock);
+ pthread_cond_destroy(&sched->notify);
+
+ while (!EMPTY_HEAP(&sched->heap)) {
+ event_t *e = (event_t *)*HHEAD(&sched->heap);
+ heap_delmin(&sched->heap);
+ evsched_event_free(e);
+ }
+
+ heap_deinit(&sched->heap);
+
+ if (sched->thread != NULL) {
+ dt_delete(&sched->thread);
+ }
+
+ /* Clear the structure. */
+ memset(sched, 0, sizeof(evsched_t));
+}
+
+event_t *evsched_event_create(evsched_t *sched, event_cb_t cb, void *data)
+{
+ /* Create event. */
+ if (sched == NULL) {
+ return NULL;
+ }
+
+ /* Allocate. */
+ event_t *e = malloc(sizeof(event_t));
+ if (e == NULL) {
+ return NULL;
+ }
+
+ /* Initialize. */
+ memset(e, 0, sizeof(event_t));
+ e->sched = sched;
+ e->cb = cb;
+ e->data = data;
+ e->hpos.pos = 0;
+
+ return e;
+}
+
+void evsched_event_free(event_t *ev)
+{
+ if (ev == NULL) {
+ return;
+ }
+
+ free(ev);
+}
+
+int evsched_schedule(event_t *ev, uint32_t dt)
+{
+ if (ev == NULL || ev->sched == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct timeval new_time = timeval_in(dt);
+
+ evsched_t *sched = ev->sched;
+
+ /* Lock calendar. */
+ pthread_mutex_lock(&sched->heap_lock);
+
+ ev->tv = new_time;
+
+ /* Make sure it's not already enqueued. */
+ int found = heap_find(&sched->heap, (heap_val_t *)ev);
+ if (found > 0) {
+ /* "Replacing" with itself -- just repositioning it. */
+ heap_replace(&sched->heap, found, (heap_val_t *)ev);
+ } else {
+ heap_insert(&sched->heap, (heap_val_t *)ev);
+ }
+
+ /* Unlock calendar. */
+ pthread_cond_signal(&sched->notify);
+ pthread_mutex_unlock(&sched->heap_lock);
+
+ return KNOT_EOK;
+}
+
+int evsched_cancel(event_t *ev)
+{
+ if (ev == NULL || ev->sched == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ evsched_t *sched = ev->sched;
+
+ /* Lock calendar. */
+ pthread_mutex_lock(&sched->heap_lock);
+
+ int found = heap_find(&sched->heap, (heap_val_t *)ev);
+ if (found > 0) {
+ heap_delete(&sched->heap, found);
+ pthread_cond_signal(&sched->notify);
+ }
+
+ /* Unlock calendar. */
+ pthread_mutex_unlock(&sched->heap_lock);
+
+ /* Reset event timer. */
+ memset(&ev->tv, 0, sizeof(struct timeval));
+
+ return KNOT_EOK;
+}
+
+void evsched_start(evsched_t *sched)
+{
+ dt_start(sched->thread);
+}
+
+void evsched_stop(evsched_t *sched)
+{
+ pthread_mutex_lock(&sched->heap_lock);
+ dt_stop(sched->thread);
+ pthread_cond_signal(&sched->notify);
+ pthread_mutex_unlock(&sched->heap_lock);
+}
+
+void evsched_join(evsched_t *sched)
+{
+ dt_join(sched->thread);
+}
+
+void evsched_pause(evsched_t *sched)
+{
+ pthread_mutex_lock(&sched->heap_lock);
+ sched->paused = true;
+ pthread_mutex_unlock(&sched->heap_lock);
+}
+
+void evsched_resume(evsched_t *sched)
+{
+ pthread_mutex_lock(&sched->heap_lock);
+ sched->paused = false;
+ pthread_cond_signal(&sched->notify);
+ pthread_mutex_unlock(&sched->heap_lock);
+}