1 /*
2 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3 *
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
9 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
11 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
13 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14 * PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 /* $Id: timer.c,v 1.25 2020/09/14 08:40:44 florian Exp $ */
18
19 /*! \file */
20
21 #include <sys/time.h>
22 #include <stdlib.h>
23 #include <time.h>
24 #include <isc/heap.h>
25 #include <isc/task.h>
26 #include <isc/timer.h>
27 #include <isc/util.h>
28
29 #include "timer_p.h"
30
31 struct isc_timer {
32 /*! Not locked. */
33 isc_timermgr_t * manager;
34 /*! Locked by timer lock. */
35 unsigned int references;
36 struct timespec idle;
37 /*! Locked by manager lock. */
38 struct timespec interval;
39 isc_task_t * task;
40 isc_taskaction_t action;
41 void * arg;
42 unsigned int index;
43 struct timespec due;
44 LINK(isc_timer_t) link;
45 };
46
47 struct isc_timermgr {
48 /* Not locked. */
49 /* Locked by manager lock. */
50 int done;
51 LIST(isc_timer_t) timers;
52 unsigned int nscheduled;
53 struct timespec due;
54 unsigned int refs;
55 isc_heap_t * heap;
56 };
57
58 /*%
59 * The following are intended for internal use (indicated by "isc__"
60 * prefix) but are not declared as static, allowing direct access from
61 * unit tests etc.
62 */
63
64 /*!
65 * If the manager is supposed to be shared, there can be only one.
66 */
67 static isc_timermgr_t *timermgr = NULL;
68
69 static inline isc_result_t
schedule(isc_timer_t * timer)70 schedule(isc_timer_t *timer) {
71 isc_result_t result;
72 isc_timermgr_t *manager;
73 struct timespec due;
74
75 /*!
76 * Note: the caller must ensure locking.
77 */
78
79 manager = timer->manager;
80
81 /*
82 * Compute the new due time.
83 */
84 due = timer->idle;
85
86 /*
87 * Schedule the timer.
88 */
89
90 if (timer->index > 0) {
91 /*
92 * Already scheduled.
93 */
94 if (timespeccmp(&due, &timer->due, <))
95 isc_heap_increased(manager->heap, timer->index);
96 else if (timespeccmp(&due, &timer->due, >))
97 isc_heap_decreased(manager->heap, timer->index);
98
99 timer->due = due;
100 } else {
101 timer->due = due;
102 result = isc_heap_insert(manager->heap, timer);
103 if (result != ISC_R_SUCCESS) {
104 INSIST(result == ISC_R_NOMEMORY);
105 return (ISC_R_NOMEMORY);
106 }
107 manager->nscheduled++;
108 }
109
110 /*
111 * If this timer is at the head of the queue, we need to ensure
112 * that we won't miss it if it has a more recent due time than
113 * the current "next" timer. We do this either by waking up the
114 * run thread, or explicitly setting the value in the manager.
115 */
116 if (timer->index == 1 && timespeccmp(&timer->due, &manager->due, <))
117 manager->due = timer->due;
118
119 return (ISC_R_SUCCESS);
120 }
121
122 static inline void
deschedule(isc_timer_t * timer)123 deschedule(isc_timer_t *timer) {
124 isc_timermgr_t *manager;
125
126 /*
127 * The caller must ensure locking.
128 */
129
130 manager = timer->manager;
131 if (timer->index > 0) {
132 isc_heap_delete(manager->heap, timer->index);
133 timer->index = 0;
134 INSIST(manager->nscheduled > 0);
135 manager->nscheduled--;
136 }
137 }
138
139 static void
destroy(isc_timer_t * timer)140 destroy(isc_timer_t *timer) {
141 isc_timermgr_t *manager = timer->manager;
142
143 /*
144 * The caller must ensure it is safe to destroy the timer.
145 */
146
147 (void)isc_task_purgerange(timer->task,
148 timer,
149 ISC_TIMEREVENT_FIRSTEVENT,
150 ISC_TIMEREVENT_LASTEVENT,
151 NULL);
152 deschedule(timer);
153 UNLINK(manager->timers, timer, link);
154
155 isc_task_detach(&timer->task);
156 free(timer);
157 }
158
159 isc_result_t
isc_timer_create(isc_timermgr_t * manager0,const struct timespec * interval,isc_task_t * task,isc_taskaction_t action,void * arg,isc_timer_t ** timerp)160 isc_timer_create(isc_timermgr_t *manager0, const struct timespec *interval,
161 isc_task_t *task, isc_taskaction_t action, void *arg,
162 isc_timer_t **timerp)
163 {
164 isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
165 isc_timer_t *timer;
166 isc_result_t result;
167 struct timespec now;
168
169 /*
170 * Create a new 'type' timer managed by 'manager'. The timers
171 * parameters are specified by 'interval'. Events
172 * will be posted to 'task' and when dispatched 'action' will be
173 * called with 'arg' as the arg value. The new timer is returned
174 * in 'timerp'.
175 */
176
177 REQUIRE(task != NULL);
178 REQUIRE(action != NULL);
179 REQUIRE(interval != NULL);
180 REQUIRE(timespecisset(interval));
181 REQUIRE(timerp != NULL && *timerp == NULL);
182
183 /*
184 * Get current time.
185 */
186 clock_gettime(CLOCK_MONOTONIC, &now);
187
188 timer = malloc(sizeof(*timer));
189 if (timer == NULL)
190 return (ISC_R_NOMEMORY);
191
192 timer->manager = manager;
193 timer->references = 1;
194
195 if (timespecisset(interval))
196 timespecadd(&now, interval, &timer->idle);
197
198 timer->interval = *interval;
199 timer->task = NULL;
200 isc_task_attach(task, &timer->task);
201 timer->action = action;
202 /*
203 * Removing the const attribute from "arg" is the best of two
204 * evils here. If the timer->arg member is made const, then
205 * it affects a great many recipients of the timer event
206 * which did not pass in an "arg" that was truly const.
207 * Changing isc_timer_create() to not have "arg" prototyped as const,
208 * though, can cause compilers warnings for calls that *do*
209 * have a truly const arg. The caller will have to carefully
210 * keep track of whether arg started as a true const.
211 */
212 DE_CONST(arg, timer->arg);
213 timer->index = 0;
214 ISC_LINK_INIT(timer, link);
215
216 result = schedule(timer);
217 if (result == ISC_R_SUCCESS)
218 APPEND(manager->timers, timer, link);
219
220 if (result != ISC_R_SUCCESS) {
221 isc_task_detach(&timer->task);
222 free(timer);
223 return (result);
224 }
225
226 *timerp = (isc_timer_t *)timer;
227
228 return (ISC_R_SUCCESS);
229 }
230
231 isc_result_t
isc_timer_reset(isc_timer_t * timer,const struct timespec * interval,int purge)232 isc_timer_reset(isc_timer_t *timer, const struct timespec *interval,
233 int purge)
234 {
235 struct timespec now;
236 isc_result_t result;
237
238 /*
239 * Change the timer's type, expires, and interval values to the given
240 * values. If 'purge' is 1, any pending events from this timer
241 * are purged from its task's event queue.
242 */
243
244 REQUIRE(interval != NULL);
245 REQUIRE(timespecisset(interval));
246
247 /*
248 * Get current time.
249 */
250 clock_gettime(CLOCK_MONOTONIC, &now);
251
252 if (purge)
253 (void)isc_task_purgerange(timer->task,
254 timer,
255 ISC_TIMEREVENT_FIRSTEVENT,
256 ISC_TIMEREVENT_LASTEVENT,
257 NULL);
258 timer->interval = *interval;
259 if (timespecisset(interval)) {
260 timespecadd(&now, interval, &timer->idle);
261 } else {
262 timespecclear(&timer->idle);
263 }
264
265 result = schedule(timer);
266
267 return (result);
268 }
269
270 void
isc_timer_touch(isc_timer_t * timer)271 isc_timer_touch(isc_timer_t *timer) {
272 struct timespec now;
273
274 /*
275 * Set the last-touched time of 'timer' to the current time.
276 */
277
278 clock_gettime(CLOCK_MONOTONIC, &now);
279 timespecadd(&now, &timer->interval, &timer->idle);
280 }
281
282 void
isc_timer_detach(isc_timer_t ** timerp)283 isc_timer_detach(isc_timer_t **timerp) {
284 isc_timer_t *timer;
285 int free_timer = 0;
286
287 /*
288 * Detach *timerp from its timer.
289 */
290
291 REQUIRE(timerp != NULL);
292 timer = (isc_timer_t *)*timerp;
293
294 REQUIRE(timer->references > 0);
295 timer->references--;
296 if (timer->references == 0)
297 free_timer = 1;
298
299 if (free_timer)
300 destroy(timer);
301
302 *timerp = NULL;
303 }
304
305 static void
dispatch(isc_timermgr_t * manager,struct timespec * now)306 dispatch(isc_timermgr_t *manager, struct timespec *now) {
307 int done = 0, post_event, need_schedule;
308 isc_timerevent_t *event;
309 isc_eventtype_t type = 0;
310 isc_timer_t *timer;
311 isc_result_t result;
312 int idle;
313
314 /*!
315 * The caller must be holding the manager lock.
316 */
317
318 while (manager->nscheduled > 0 && !done) {
319 timer = isc_heap_element(manager->heap, 1);
320 INSIST(timer != NULL);
321 if (timespeccmp(now, &timer->due, >=)) {
322 idle = 0;
323
324 if (timespecisset(&timer->idle) && timespeccmp(now,
325 &timer->idle, >=)) {
326 idle = 1;
327 }
328 if (idle) {
329 type = ISC_TIMEREVENT_IDLE;
330 post_event = 1;
331 need_schedule = 0;
332 } else {
333 /*
334 * Idle timer has been touched;
335 * reschedule.
336 */
337 post_event = 0;
338 need_schedule = 1;
339 }
340
341 if (post_event) {
342 /*
343 * XXX We could preallocate this event.
344 */
345 event = (isc_timerevent_t *)isc_event_allocate(
346 timer,
347 type,
348 timer->action,
349 timer->arg,
350 sizeof(*event));
351
352 if (event != NULL) {
353 event->due = timer->due;
354 isc_task_send(timer->task,
355 ISC_EVENT_PTR(&event));
356 } else
357 UNEXPECTED_ERROR(__FILE__, __LINE__, "%s",
358 "couldn't allocate event");
359 }
360
361 timer->index = 0;
362 isc_heap_delete(manager->heap, 1);
363 manager->nscheduled--;
364
365 if (need_schedule) {
366 result = schedule(timer);
367 if (result != ISC_R_SUCCESS)
368 UNEXPECTED_ERROR(__FILE__, __LINE__,
369 "%s: %u",
370 "couldn't schedule timer",
371 result);
372 }
373 } else {
374 manager->due = timer->due;
375 done = 1;
376 }
377 }
378 }
379
380 static int
sooner(void * v1,void * v2)381 sooner(void *v1, void *v2) {
382 isc_timer_t *t1, *t2;
383
384 t1 = v1;
385 t2 = v2;
386
387 if (timespeccmp(&t1->due, &t2->due, <))
388 return (1);
389 return (0);
390 }
391
392 static void
set_index(void * what,unsigned int index)393 set_index(void *what, unsigned int index) {
394 isc_timer_t *timer;
395
396 timer = what;
397
398 timer->index = index;
399 }
400
401 isc_result_t
isc_timermgr_create(isc_timermgr_t ** managerp)402 isc_timermgr_create(isc_timermgr_t **managerp) {
403 isc_timermgr_t *manager;
404 isc_result_t result;
405
406 /*
407 * Create a timer manager.
408 */
409
410 REQUIRE(managerp != NULL && *managerp == NULL);
411
412 if (timermgr != NULL) {
413 timermgr->refs++;
414 *managerp = (isc_timermgr_t *)timermgr;
415 return (ISC_R_SUCCESS);
416 }
417
418 manager = malloc(sizeof(*manager));
419 if (manager == NULL)
420 return (ISC_R_NOMEMORY);
421
422 manager->done = 0;
423 INIT_LIST(manager->timers);
424 manager->nscheduled = 0;
425 timespecclear(&manager->due);
426 manager->heap = NULL;
427 result = isc_heap_create(sooner, set_index, 0, &manager->heap);
428 if (result != ISC_R_SUCCESS) {
429 INSIST(result == ISC_R_NOMEMORY);
430 free(manager);
431 return (ISC_R_NOMEMORY);
432 }
433 manager->refs = 1;
434 timermgr = manager;
435
436 *managerp = (isc_timermgr_t *)manager;
437
438 return (ISC_R_SUCCESS);
439 }
440
441 void
isc_timermgr_destroy(isc_timermgr_t ** managerp)442 isc_timermgr_destroy(isc_timermgr_t **managerp) {
443 isc_timermgr_t *manager;
444
445 /*
446 * Destroy a timer manager.
447 */
448
449 REQUIRE(managerp != NULL);
450 manager = (isc_timermgr_t *)*managerp;
451
452 manager->refs--;
453 if (manager->refs > 0) {
454 *managerp = NULL;
455 return;
456 }
457 timermgr = NULL;
458
459 isc_timermgr_dispatch((isc_timermgr_t *)manager);
460
461 REQUIRE(EMPTY(manager->timers));
462 manager->done = 1;
463
464 /*
465 * Clean up.
466 */
467 isc_heap_destroy(&manager->heap);
468 free(manager);
469
470 *managerp = NULL;
471
472 timermgr = NULL;
473 }
474
475 isc_result_t
isc_timermgr_nextevent(isc_timermgr_t * manager0,struct timespec * when)476 isc_timermgr_nextevent(isc_timermgr_t *manager0, struct timespec *when) {
477 isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
478
479 if (manager == NULL)
480 manager = timermgr;
481 if (manager == NULL || manager->nscheduled == 0)
482 return (ISC_R_NOTFOUND);
483 *when = manager->due;
484 return (ISC_R_SUCCESS);
485 }
486
487 void
isc_timermgr_dispatch(isc_timermgr_t * manager0)488 isc_timermgr_dispatch(isc_timermgr_t *manager0) {
489 isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
490 struct timespec now;
491
492 if (manager == NULL)
493 manager = timermgr;
494 if (manager == NULL)
495 return;
496 clock_gettime(CLOCK_MONOTONIC, &now);
497 dispatch(manager, &now);
498 }
499
500