15185a700Sflorian /* 25185a700Sflorian * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 35185a700Sflorian * 45185a700Sflorian * Permission to use, copy, modify, and/or distribute this software for any 55185a700Sflorian * purpose with or without fee is hereby granted, provided that the above 65185a700Sflorian * copyright notice and this permission notice appear in all copies. 75185a700Sflorian * 85185a700Sflorian * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 95185a700Sflorian * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 105185a700Sflorian * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 115185a700Sflorian * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 125185a700Sflorian * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 135185a700Sflorian * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 145185a700Sflorian * PERFORMANCE OF THIS SOFTWARE. 155185a700Sflorian */ 165185a700Sflorian 17*ad5cf538Sjung /* $Id: timer.c,v 1.21 2020/02/22 19:50:05 jung Exp $ */ 185185a700Sflorian 195185a700Sflorian /*! \file */ 205185a700Sflorian 215185a700Sflorian 225185a700Sflorian #include <stdlib.h> 235185a700Sflorian #include <isc/heap.h> 245185a700Sflorian #include <isc/task.h> 255185a700Sflorian #include <isc/time.h> 265185a700Sflorian #include <isc/timer.h> 275185a700Sflorian #include <isc/util.h> 285185a700Sflorian 295185a700Sflorian #include "timer_p.h" 305185a700Sflorian 318b553854Sflorian typedef struct isc_timer isc_timer_t; 328b553854Sflorian typedef struct isc_timermgr isc_timermgr_t; 335185a700Sflorian 348b553854Sflorian struct isc_timer { 355185a700Sflorian /*! Not locked. */ 368b553854Sflorian isc_timermgr_t * manager; 375185a700Sflorian /*! Locked by timer lock. */ 385185a700Sflorian unsigned int references; 397238a213Sflorian struct timespec idle; 405185a700Sflorian /*! Locked by manager lock. */ 417238a213Sflorian struct timespec interval; 425185a700Sflorian isc_task_t * task; 435185a700Sflorian isc_taskaction_t action; 445185a700Sflorian void * arg; 455185a700Sflorian unsigned int index; 467238a213Sflorian struct timespec due; 478b553854Sflorian LINK(isc_timer_t) link; 485185a700Sflorian }; 495185a700Sflorian 508b553854Sflorian struct isc_timermgr { 515185a700Sflorian /* Not locked. */ 525185a700Sflorian /* Locked by manager lock. */ 535185a700Sflorian isc_boolean_t done; 548b553854Sflorian LIST(isc_timer_t) timers; 555185a700Sflorian unsigned int nscheduled; 567238a213Sflorian struct timespec due; 575185a700Sflorian unsigned int refs; 585185a700Sflorian isc_heap_t * heap; 595185a700Sflorian }; 605185a700Sflorian 615185a700Sflorian /*% 625185a700Sflorian * The following are intended for internal use (indicated by "isc__" 635185a700Sflorian * prefix) but are not declared as static, allowing direct access from 645185a700Sflorian * unit tests etc. 655185a700Sflorian */ 665185a700Sflorian 675185a700Sflorian /*! 685185a700Sflorian * If the manager is supposed to be shared, there can be only one. 695185a700Sflorian */ 708b553854Sflorian static isc_timermgr_t *timermgr = NULL; 715185a700Sflorian 725185a700Sflorian static inline isc_result_t 73*ad5cf538Sjung schedule(isc_timer_t *timer) { 745185a700Sflorian isc_result_t result; 758b553854Sflorian isc_timermgr_t *manager; 767238a213Sflorian struct timespec due; 775185a700Sflorian 785185a700Sflorian /*! 795185a700Sflorian * Note: the caller must ensure locking. 805185a700Sflorian */ 815185a700Sflorian 825185a700Sflorian manager = timer->manager; 835185a700Sflorian 845185a700Sflorian /* 855185a700Sflorian * Compute the new due time. 865185a700Sflorian */ 875185a700Sflorian due = timer->idle; 885185a700Sflorian 895185a700Sflorian /* 905185a700Sflorian * Schedule the timer. 915185a700Sflorian */ 925185a700Sflorian 935185a700Sflorian if (timer->index > 0) { 945185a700Sflorian /* 955185a700Sflorian * Already scheduled. 965185a700Sflorian */ 97ba6f4614Sflorian if (timespeccmp(&due, &timer->due, <)) 985185a700Sflorian isc_heap_increased(manager->heap, timer->index); 99ba6f4614Sflorian else if (timespeccmp(&due, &timer->due, >)) 1005185a700Sflorian isc_heap_decreased(manager->heap, timer->index); 101ba6f4614Sflorian 102ba6f4614Sflorian timer->due = due; 1035185a700Sflorian } else { 1045185a700Sflorian timer->due = due; 1055185a700Sflorian result = isc_heap_insert(manager->heap, timer); 1065185a700Sflorian if (result != ISC_R_SUCCESS) { 1075185a700Sflorian INSIST(result == ISC_R_NOMEMORY); 1085185a700Sflorian return (ISC_R_NOMEMORY); 1095185a700Sflorian } 1105185a700Sflorian manager->nscheduled++; 1115185a700Sflorian } 1125185a700Sflorian 1135185a700Sflorian /* 1145185a700Sflorian * If this timer is at the head of the queue, we need to ensure 1155185a700Sflorian * that we won't miss it if it has a more recent due time than 1165185a700Sflorian * the current "next" timer. We do this either by waking up the 1175185a700Sflorian * run thread, or explicitly setting the value in the manager. 1185185a700Sflorian */ 119ba6f4614Sflorian if (timer->index == 1 && timespeccmp(&timer->due, &manager->due, <)) 1205185a700Sflorian manager->due = timer->due; 1215185a700Sflorian 1225185a700Sflorian return (ISC_R_SUCCESS); 1235185a700Sflorian } 1245185a700Sflorian 1255185a700Sflorian static inline void 1268b553854Sflorian deschedule(isc_timer_t *timer) { 1278b553854Sflorian isc_timermgr_t *manager; 1285185a700Sflorian 1295185a700Sflorian /* 1305185a700Sflorian * The caller must ensure locking. 1315185a700Sflorian */ 1325185a700Sflorian 1335185a700Sflorian manager = timer->manager; 1345185a700Sflorian if (timer->index > 0) { 1355185a700Sflorian isc_heap_delete(manager->heap, timer->index); 1365185a700Sflorian timer->index = 0; 1375185a700Sflorian INSIST(manager->nscheduled > 0); 1385185a700Sflorian manager->nscheduled--; 1395185a700Sflorian } 1405185a700Sflorian } 1415185a700Sflorian 1425185a700Sflorian static void 1438b553854Sflorian destroy(isc_timer_t *timer) { 1448b553854Sflorian isc_timermgr_t *manager = timer->manager; 1455185a700Sflorian 1465185a700Sflorian /* 1475185a700Sflorian * The caller must ensure it is safe to destroy the timer. 1485185a700Sflorian */ 1495185a700Sflorian 1505185a700Sflorian (void)isc_task_purgerange(timer->task, 1515185a700Sflorian timer, 1525185a700Sflorian ISC_TIMEREVENT_FIRSTEVENT, 1535185a700Sflorian ISC_TIMEREVENT_LASTEVENT, 1545185a700Sflorian NULL); 1555185a700Sflorian deschedule(timer); 1565185a700Sflorian UNLINK(manager->timers, timer, link); 1575185a700Sflorian 1585185a700Sflorian isc_task_detach(&timer->task); 1595185a700Sflorian free(timer); 1605185a700Sflorian } 1615185a700Sflorian 1625185a700Sflorian isc_result_t 1638b553854Sflorian isc_timer_create(isc_timermgr_t *manager0, const struct timespec *interval, 1645185a700Sflorian isc_task_t *task, isc_taskaction_t action, void *arg, 1655185a700Sflorian isc_timer_t **timerp) 1665185a700Sflorian { 1678b553854Sflorian isc_timermgr_t *manager = (isc_timermgr_t *)manager0; 1688b553854Sflorian isc_timer_t *timer; 1695185a700Sflorian isc_result_t result; 1707238a213Sflorian struct timespec now; 1715185a700Sflorian 1725185a700Sflorian /* 1735185a700Sflorian * Create a new 'type' timer managed by 'manager'. The timers 1745ec36317Sflorian * parameters are specified by 'interval'. Events 1755185a700Sflorian * will be posted to 'task' and when dispatched 'action' will be 1765185a700Sflorian * called with 'arg' as the arg value. The new timer is returned 1775185a700Sflorian * in 'timerp'. 1785185a700Sflorian */ 1795185a700Sflorian 1805185a700Sflorian REQUIRE(task != NULL); 1815185a700Sflorian REQUIRE(action != NULL); 1821eb893f3Sflorian REQUIRE(interval != NULL); 183396be909Sflorian REQUIRE(timespecisset(interval)); 1845185a700Sflorian REQUIRE(timerp != NULL && *timerp == NULL); 1855185a700Sflorian 1865185a700Sflorian /* 1875185a700Sflorian * Get current time. 1885185a700Sflorian */ 189b53d8310Sflorian clock_gettime(CLOCK_MONOTONIC, &now); 1905185a700Sflorian 1915185a700Sflorian timer = malloc(sizeof(*timer)); 1925185a700Sflorian if (timer == NULL) 1935185a700Sflorian return (ISC_R_NOMEMORY); 1945185a700Sflorian 1955185a700Sflorian timer->manager = manager; 1965185a700Sflorian timer->references = 1; 1975185a700Sflorian 198ffbbf1a1Sflorian if (timespecisset(interval)) 199ffbbf1a1Sflorian timespecadd(&now, interval, &timer->idle); 2005185a700Sflorian 2015185a700Sflorian timer->interval = *interval; 2025185a700Sflorian timer->task = NULL; 2035185a700Sflorian isc_task_attach(task, &timer->task); 2045185a700Sflorian timer->action = action; 2055185a700Sflorian /* 2065185a700Sflorian * Removing the const attribute from "arg" is the best of two 2075185a700Sflorian * evils here. If the timer->arg member is made const, then 2085185a700Sflorian * it affects a great many recipients of the timer event 2095185a700Sflorian * which did not pass in an "arg" that was truly const. 2105185a700Sflorian * Changing isc_timer_create() to not have "arg" prototyped as const, 2115185a700Sflorian * though, can cause compilers warnings for calls that *do* 2125185a700Sflorian * have a truly const arg. The caller will have to carefully 2135185a700Sflorian * keep track of whether arg started as a true const. 2145185a700Sflorian */ 2155185a700Sflorian DE_CONST(arg, timer->arg); 2165185a700Sflorian timer->index = 0; 2175185a700Sflorian ISC_LINK_INIT(timer, link); 2185185a700Sflorian 219*ad5cf538Sjung result = schedule(timer); 2205185a700Sflorian if (result == ISC_R_SUCCESS) 2215185a700Sflorian APPEND(manager->timers, timer, link); 2225185a700Sflorian 2235185a700Sflorian if (result != ISC_R_SUCCESS) { 2245185a700Sflorian isc_task_detach(&timer->task); 2255185a700Sflorian free(timer); 2265185a700Sflorian return (result); 2275185a700Sflorian } 2285185a700Sflorian 2295185a700Sflorian *timerp = (isc_timer_t *)timer; 2305185a700Sflorian 2315185a700Sflorian return (ISC_R_SUCCESS); 2325185a700Sflorian } 2335185a700Sflorian 2345185a700Sflorian isc_result_t 2358b553854Sflorian isc_timer_reset(isc_timer_t *timer, const struct timespec *interval, 2365185a700Sflorian isc_boolean_t purge) 2375185a700Sflorian { 2387238a213Sflorian struct timespec now; 2395185a700Sflorian isc_result_t result; 2405185a700Sflorian 2415185a700Sflorian /* 2425185a700Sflorian * Change the timer's type, expires, and interval values to the given 2435185a700Sflorian * values. If 'purge' is ISC_TRUE, any pending events from this timer 2445185a700Sflorian * are purged from its task's event queue. 2455185a700Sflorian */ 2465185a700Sflorian 2471eb893f3Sflorian REQUIRE(interval != NULL); 248396be909Sflorian REQUIRE(timespecisset(interval)); 2495185a700Sflorian 2505185a700Sflorian /* 2515185a700Sflorian * Get current time. 2525185a700Sflorian */ 253b53d8310Sflorian clock_gettime(CLOCK_MONOTONIC, &now); 2545185a700Sflorian 2555185a700Sflorian if (purge) 2565185a700Sflorian (void)isc_task_purgerange(timer->task, 2575185a700Sflorian timer, 2585185a700Sflorian ISC_TIMEREVENT_FIRSTEVENT, 2595185a700Sflorian ISC_TIMEREVENT_LASTEVENT, 2605185a700Sflorian NULL); 2615185a700Sflorian timer->interval = *interval; 262396be909Sflorian if (timespecisset(interval)) { 263ffbbf1a1Sflorian timespecadd(&now, interval, &timer->idle); 2645185a700Sflorian } else { 265bb7ec108Sflorian timespecclear(&timer->idle); 2665185a700Sflorian } 2675185a700Sflorian 268*ad5cf538Sjung result = schedule(timer); 2695185a700Sflorian 2705185a700Sflorian return (result); 2715185a700Sflorian } 2725185a700Sflorian 273ffbbf1a1Sflorian void 2748b553854Sflorian isc_timer_touch(isc_timer_t *timer) { 2757238a213Sflorian struct timespec now; 2765185a700Sflorian 2775185a700Sflorian /* 2785185a700Sflorian * Set the last-touched time of 'timer' to the current time. 2795185a700Sflorian */ 2805185a700Sflorian 2815185a700Sflorian 282b53d8310Sflorian clock_gettime(CLOCK_MONOTONIC, &now); 283ffbbf1a1Sflorian timespecadd(&now, &timer->interval, &timer->idle); 2845185a700Sflorian } 2855185a700Sflorian 2865185a700Sflorian void 2878b553854Sflorian isc_timer_detach(isc_timer_t **timerp) { 2888b553854Sflorian isc_timer_t *timer; 2895185a700Sflorian isc_boolean_t free_timer = ISC_FALSE; 2905185a700Sflorian 2915185a700Sflorian /* 2925185a700Sflorian * Detach *timerp from its timer. 2935185a700Sflorian */ 2945185a700Sflorian 2955185a700Sflorian REQUIRE(timerp != NULL); 2968b553854Sflorian timer = (isc_timer_t *)*timerp; 2975185a700Sflorian 2985185a700Sflorian REQUIRE(timer->references > 0); 2995185a700Sflorian timer->references--; 3005185a700Sflorian if (timer->references == 0) 3015185a700Sflorian free_timer = ISC_TRUE; 3025185a700Sflorian 3035185a700Sflorian if (free_timer) 3045185a700Sflorian destroy(timer); 3055185a700Sflorian 3065185a700Sflorian *timerp = NULL; 3075185a700Sflorian } 3085185a700Sflorian 3095185a700Sflorian static void 3108b553854Sflorian dispatch(isc_timermgr_t *manager, struct timespec *now) { 3115185a700Sflorian isc_boolean_t done = ISC_FALSE, post_event, need_schedule; 3125185a700Sflorian isc_timerevent_t *event; 3135185a700Sflorian isc_eventtype_t type = 0; 3148b553854Sflorian isc_timer_t *timer; 3155185a700Sflorian isc_result_t result; 3165185a700Sflorian isc_boolean_t idle; 3175185a700Sflorian 3185185a700Sflorian /*! 3195185a700Sflorian * The caller must be holding the manager lock. 3205185a700Sflorian */ 3215185a700Sflorian 3225185a700Sflorian while (manager->nscheduled > 0 && !done) { 3235185a700Sflorian timer = isc_heap_element(manager->heap, 1); 32475fe954bSflorian INSIST(timer != NULL); 325ba6f4614Sflorian if (timespeccmp(now, &timer->due, >=)) { 3265185a700Sflorian idle = ISC_FALSE; 3275185a700Sflorian 328ba6f4614Sflorian if (timespecisset(&timer->idle) && timespeccmp(now, 329ba6f4614Sflorian &timer->idle, >=)) { 3305185a700Sflorian idle = ISC_TRUE; 3315185a700Sflorian } 3325185a700Sflorian if (idle) { 3335185a700Sflorian type = ISC_TIMEREVENT_IDLE; 3345185a700Sflorian post_event = ISC_TRUE; 3355185a700Sflorian need_schedule = ISC_FALSE; 3365185a700Sflorian } else { 3375185a700Sflorian /* 3385185a700Sflorian * Idle timer has been touched; 3395185a700Sflorian * reschedule. 3405185a700Sflorian */ 3415185a700Sflorian post_event = ISC_FALSE; 3425185a700Sflorian need_schedule = ISC_TRUE; 3435185a700Sflorian } 3445185a700Sflorian 3455185a700Sflorian if (post_event) { 3465185a700Sflorian /* 3475185a700Sflorian * XXX We could preallocate this event. 3485185a700Sflorian */ 3495185a700Sflorian event = (isc_timerevent_t *)isc_event_allocate( 3505185a700Sflorian timer, 3515185a700Sflorian type, 3525185a700Sflorian timer->action, 3535185a700Sflorian timer->arg, 3545185a700Sflorian sizeof(*event)); 3555185a700Sflorian 3565185a700Sflorian if (event != NULL) { 3575185a700Sflorian event->due = timer->due; 3585185a700Sflorian isc_task_send(timer->task, 3595185a700Sflorian ISC_EVENT_PTR(&event)); 3605185a700Sflorian } else 3615185a700Sflorian UNEXPECTED_ERROR(__FILE__, __LINE__, "%s", 3625185a700Sflorian "couldn't allocate event"); 3635185a700Sflorian } 3645185a700Sflorian 3655185a700Sflorian timer->index = 0; 3665185a700Sflorian isc_heap_delete(manager->heap, 1); 3675185a700Sflorian manager->nscheduled--; 3685185a700Sflorian 3695185a700Sflorian if (need_schedule) { 370*ad5cf538Sjung result = schedule(timer); 3715185a700Sflorian if (result != ISC_R_SUCCESS) 3725185a700Sflorian UNEXPECTED_ERROR(__FILE__, __LINE__, 3735185a700Sflorian "%s: %u", 3745185a700Sflorian "couldn't schedule timer", 3755185a700Sflorian result); 3765185a700Sflorian } 3775185a700Sflorian } else { 3785185a700Sflorian manager->due = timer->due; 3795185a700Sflorian done = ISC_TRUE; 3805185a700Sflorian } 3815185a700Sflorian } 3825185a700Sflorian } 3835185a700Sflorian 3845185a700Sflorian static isc_boolean_t 3855185a700Sflorian sooner(void *v1, void *v2) { 3868b553854Sflorian isc_timer_t *t1, *t2; 3875185a700Sflorian 3885185a700Sflorian t1 = v1; 3895185a700Sflorian t2 = v2; 3905185a700Sflorian 391ba6f4614Sflorian if (timespeccmp(&t1->due, &t2->due, <)) 3925185a700Sflorian return (ISC_TRUE); 3935185a700Sflorian return (ISC_FALSE); 3945185a700Sflorian } 3955185a700Sflorian 3965185a700Sflorian static void 3975185a700Sflorian set_index(void *what, unsigned int index) { 3988b553854Sflorian isc_timer_t *timer; 3995185a700Sflorian 4005185a700Sflorian timer = what; 4015185a700Sflorian 4025185a700Sflorian timer->index = index; 4035185a700Sflorian } 4045185a700Sflorian 4055185a700Sflorian isc_result_t 4068b553854Sflorian isc_timermgr_create(isc_timermgr_t **managerp) { 4078b553854Sflorian isc_timermgr_t *manager; 4085185a700Sflorian isc_result_t result; 4095185a700Sflorian 4105185a700Sflorian /* 4115185a700Sflorian * Create a timer manager. 4125185a700Sflorian */ 4135185a700Sflorian 4145185a700Sflorian REQUIRE(managerp != NULL && *managerp == NULL); 4155185a700Sflorian 4165185a700Sflorian if (timermgr != NULL) { 4175185a700Sflorian timermgr->refs++; 4185185a700Sflorian *managerp = (isc_timermgr_t *)timermgr; 4195185a700Sflorian return (ISC_R_SUCCESS); 4205185a700Sflorian } 4215185a700Sflorian 4225185a700Sflorian manager = malloc(sizeof(*manager)); 4235185a700Sflorian if (manager == NULL) 4245185a700Sflorian return (ISC_R_NOMEMORY); 4255185a700Sflorian 4265185a700Sflorian manager->done = ISC_FALSE; 4275185a700Sflorian INIT_LIST(manager->timers); 4285185a700Sflorian manager->nscheduled = 0; 429bb7ec108Sflorian timespecclear(&manager->due); 4305185a700Sflorian manager->heap = NULL; 4315185a700Sflorian result = isc_heap_create(sooner, set_index, 0, &manager->heap); 4325185a700Sflorian if (result != ISC_R_SUCCESS) { 4335185a700Sflorian INSIST(result == ISC_R_NOMEMORY); 4345185a700Sflorian free(manager); 4355185a700Sflorian return (ISC_R_NOMEMORY); 4365185a700Sflorian } 4375185a700Sflorian manager->refs = 1; 4385185a700Sflorian timermgr = manager; 4395185a700Sflorian 4405185a700Sflorian *managerp = (isc_timermgr_t *)manager; 4415185a700Sflorian 4425185a700Sflorian return (ISC_R_SUCCESS); 4435185a700Sflorian } 4445185a700Sflorian 4455185a700Sflorian void 4468b553854Sflorian isc_timermgr_destroy(isc_timermgr_t **managerp) { 4478b553854Sflorian isc_timermgr_t *manager; 4485185a700Sflorian 4495185a700Sflorian /* 4505185a700Sflorian * Destroy a timer manager. 4515185a700Sflorian */ 4525185a700Sflorian 4535185a700Sflorian REQUIRE(managerp != NULL); 4548b553854Sflorian manager = (isc_timermgr_t *)*managerp; 4555185a700Sflorian 4565185a700Sflorian manager->refs--; 4575185a700Sflorian if (manager->refs > 0) { 4585185a700Sflorian *managerp = NULL; 4595185a700Sflorian return; 4605185a700Sflorian } 4615185a700Sflorian timermgr = NULL; 4625185a700Sflorian 4638b553854Sflorian isc_timermgr_dispatch((isc_timermgr_t *)manager); 4645185a700Sflorian 4655185a700Sflorian REQUIRE(EMPTY(manager->timers)); 4665185a700Sflorian manager->done = ISC_TRUE; 4675185a700Sflorian 4685185a700Sflorian /* 4695185a700Sflorian * Clean up. 4705185a700Sflorian */ 4715185a700Sflorian isc_heap_destroy(&manager->heap); 4725185a700Sflorian free(manager); 4735185a700Sflorian 4745185a700Sflorian *managerp = NULL; 4755185a700Sflorian 4765185a700Sflorian timermgr = NULL; 4775185a700Sflorian } 4785185a700Sflorian 4795185a700Sflorian isc_result_t 4808b553854Sflorian isc_timermgr_nextevent(isc_timermgr_t *manager0, struct timespec *when) { 4818b553854Sflorian isc_timermgr_t *manager = (isc_timermgr_t *)manager0; 4825185a700Sflorian 4835185a700Sflorian if (manager == NULL) 4845185a700Sflorian manager = timermgr; 4855185a700Sflorian if (manager == NULL || manager->nscheduled == 0) 4865185a700Sflorian return (ISC_R_NOTFOUND); 4875185a700Sflorian *when = manager->due; 4885185a700Sflorian return (ISC_R_SUCCESS); 4895185a700Sflorian } 4905185a700Sflorian 4915185a700Sflorian void 4928b553854Sflorian isc_timermgr_dispatch(isc_timermgr_t *manager0) { 4938b553854Sflorian isc_timermgr_t *manager = (isc_timermgr_t *)manager0; 4947238a213Sflorian struct timespec now; 4955185a700Sflorian 4965185a700Sflorian if (manager == NULL) 4975185a700Sflorian manager = timermgr; 4985185a700Sflorian if (manager == NULL) 4995185a700Sflorian return; 500b53d8310Sflorian clock_gettime(CLOCK_MONOTONIC, &now); 5015185a700Sflorian dispatch(manager, &now); 5025185a700Sflorian } 5035185a700Sflorian 504