// // Copyright 2020 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this // file was obtained (LICENSE.txt). A copy of the license may also be // found online at https://opensource.org/licenses/MIT. // #include "core/nng_impl.h" #include #include static void nni_timer_loop(void *); // XXX: replace this timer list with a minHeap based priority queue. struct nni_timer { nni_mtx t_mx; nni_cv t_wait_cv; nni_cv t_sched_cv; nni_list t_entries; nni_thr t_thr; int t_run; int t_waiting; nni_timer_node *t_active; // Must never ever be dereferenced! }; typedef struct nni_timer nni_timer; static nni_timer nni_global_timer; int nni_timer_sys_init(void) { int rv; nni_timer *timer = &nni_global_timer; memset(timer, 0, sizeof(*timer)); NNI_LIST_INIT(&timer->t_entries, nni_timer_node, t_node); nni_mtx_init(&timer->t_mx); nni_cv_init(&timer->t_sched_cv, &timer->t_mx); nni_cv_init(&timer->t_wait_cv, &timer->t_mx); if ((rv = nni_thr_init(&timer->t_thr, nni_timer_loop, timer)) != 0) { nni_timer_sys_fini(); return (rv); } timer->t_run = 1; nni_thr_run(&timer->t_thr); return (0); } void nni_timer_sys_fini(void) { nni_timer *timer = &nni_global_timer; if (timer->t_run) { nni_mtx_lock(&timer->t_mx); timer->t_run = 0; nni_cv_wake(&timer->t_sched_cv); nni_mtx_unlock(&timer->t_mx); } nni_thr_fini(&timer->t_thr); nni_cv_fini(&timer->t_wait_cv); nni_cv_fini(&timer->t_sched_cv); nni_mtx_fini(&timer->t_mx); } void nni_timer_init(nni_timer_node *node, nni_cb cb, void *arg) { node->t_cb = cb; node->t_arg = arg; } void nni_timer_fini(nni_timer_node *node) { NNI_ARG_UNUSED(node); } void nni_timer_cancel(nni_timer_node *node) { nni_timer *timer = &nni_global_timer; nni_mtx_lock(&timer->t_mx); while (timer->t_active == node) { timer->t_waiting = 1; nni_cv_wait(&timer->t_wait_cv); } if (nni_list_active(&timer->t_entries, node)) { nni_list_remove(&timer->t_entries, node); } nni_mtx_unlock(&timer->t_mx); } void nni_timer_schedule(nni_timer_node *node, nni_time when) { nni_timer *timer = &nni_global_timer; nni_mtx_lock(&timer->t_mx); node->t_expire = when; if (nni_list_active(&timer->t_entries, node)) { nni_list_remove(&timer->t_entries, node); } if (when != NNI_TIME_NEVER) { nni_timer_node *srch = nni_list_first(&timer->t_entries); while ((srch != NULL) && (srch->t_expire < node->t_expire)) { srch = nni_list_next(&timer->t_entries, srch); } if (srch != NULL) { nni_list_insert_before(&timer->t_entries, node, srch); } else { nni_list_append(&timer->t_entries, node); } if (nni_list_first(&timer->t_entries) == node) { nni_cv_wake1(&timer->t_sched_cv); } } nni_mtx_unlock(&timer->t_mx); } static void nni_timer_loop(void *arg) { nni_timer * timer = arg; nni_time now; nni_timer_node *node; nni_thr_set_name(NULL, "nng:timer"); for (;;) { nni_mtx_lock(&timer->t_mx); timer->t_active = NULL; if (timer->t_waiting) { timer->t_waiting = 0; nni_cv_wake(&timer->t_wait_cv); } if (!timer->t_run) { nni_mtx_unlock(&timer->t_mx); break; } now = nni_clock(); if ((node = nni_list_first(&timer->t_entries)) == NULL) { nni_cv_wait(&timer->t_sched_cv); nni_mtx_unlock(&timer->t_mx); continue; } if (now < node->t_expire) { // End of run, we have to wait for next. nni_cv_until(&timer->t_sched_cv, node->t_expire); nni_mtx_unlock(&timer->t_mx); continue; } nni_list_remove(&timer->t_entries, node); // Save the active node. Note that the timer callback can // free this memory or do something else with it, so it is // important that we never dereference this pointer, but // just compare the value of the pointer itself. timer->t_active = node; nni_mtx_unlock(&timer->t_mx); node->t_cb(node->t_arg); } }