timer.c revision 1.1.1.4 1 /* $NetBSD: timer.c,v 1.1.1.4 2020/05/24 19:36:46 christos Exp $ */
2
3 /*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * See the COPYRIGHT file distributed with this work for additional
11 * information regarding copyright ownership.
12 */
13
14 /*! \file */
15
16 #include <stdbool.h>
17
18 #include <isc/app.h>
19 #include <isc/condition.h>
20 #include <isc/heap.h>
21 #include <isc/log.h>
22 #include <isc/magic.h>
23 #include <isc/mem.h>
24 #include <isc/once.h>
25 #include <isc/platform.h>
26 #include <isc/print.h>
27 #include <isc/refcount.h>
28 #include <isc/task.h>
29 #include <isc/thread.h>
30 #include <isc/time.h>
31 #include <isc/timer.h>
32 #include <isc/util.h>
33
34 #ifdef OPENSSL_LEAKS
35 #include <openssl/err.h>
36 #endif /* ifdef OPENSSL_LEAKS */
37
38 #ifdef ISC_TIMER_TRACE
39 #define XTRACE(s) fprintf(stderr, "%s\n", (s))
40 #define XTRACEID(s, t) fprintf(stderr, "%s %p\n", (s), (t))
41 #define XTRACETIME(s, d) \
42 fprintf(stderr, "%s %u.%09u\n", (s), (d).seconds, (d).nanoseconds)
43 #define XTRACETIME2(s, d, n) \
44 fprintf(stderr, "%s %u.%09u %u.%09u\n", (s), (d).seconds, \
45 (d).nanoseconds, (n).seconds, (n).nanoseconds)
46 #define XTRACETIMER(s, t, d) \
47 fprintf(stderr, "%s %p %u.%09u\n", (s), (t), (d).seconds, \
48 (d).nanoseconds)
49 #else /* ifdef ISC_TIMER_TRACE */
50 #define XTRACE(s)
51 #define XTRACEID(s, t)
52 #define XTRACETIME(s, d)
53 #define XTRACETIME2(s, d, n)
54 #define XTRACETIMER(s, t, d)
55 #endif /* ISC_TIMER_TRACE */
56
57 #define TIMER_MAGIC ISC_MAGIC('T', 'I', 'M', 'R')
58 #define VALID_TIMER(t) ISC_MAGIC_VALID(t, TIMER_MAGIC)
59
60 typedef struct isc__timer isc__timer_t;
61 typedef struct isc__timermgr isc__timermgr_t;
62
63 struct isc__timer {
64 /*! Not locked. */
65 isc_timer_t common;
66 isc__timermgr_t *manager;
67 isc_mutex_t lock;
68 isc_refcount_t references;
69 /*! Locked by timer lock. */
70 isc_time_t idle;
71 /*! Locked by manager lock. */
72 isc_timertype_t type;
73 isc_time_t expires;
74 isc_interval_t interval;
75 isc_task_t *task;
76 isc_taskaction_t action;
77 void *arg;
78 unsigned int index;
79 isc_time_t due;
80 LINK(isc__timer_t) link;
81 };
82
83 #define TIMER_MANAGER_MAGIC ISC_MAGIC('T', 'I', 'M', 'M')
84 #define VALID_MANAGER(m) ISC_MAGIC_VALID(m, TIMER_MANAGER_MAGIC)
85
86 struct isc__timermgr {
87 /* Not locked. */
88 isc_timermgr_t common;
89 isc_mem_t *mctx;
90 isc_mutex_t lock;
91 /* Locked by manager lock. */
92 bool done;
93 LIST(isc__timer_t) timers;
94 unsigned int nscheduled;
95 isc_time_t due;
96 isc_condition_t wakeup;
97 isc_thread_t thread;
98 isc_heap_t *heap;
99 };
100
101 void
102 isc_timermgr_poke(isc_timermgr_t *manager0);
103
104 static inline isc_result_t
105 schedule(isc__timer_t *timer, isc_time_t *now, bool signal_ok) {
106 isc_result_t result;
107 isc__timermgr_t *manager;
108 isc_time_t due;
109 int cmp;
110
111 /*!
112 * Note: the caller must ensure locking.
113 */
114
115 REQUIRE(timer->type != isc_timertype_inactive);
116
117 manager = timer->manager;
118
119 /*
120 * Compute the new due time.
121 */
122 if (timer->type != isc_timertype_once) {
123 result = isc_time_add(now, &timer->interval, &due);
124 if (result != ISC_R_SUCCESS) {
125 return (result);
126 }
127 if (timer->type == isc_timertype_limited &&
128 isc_time_compare(&timer->expires, &due) < 0)
129 {
130 due = timer->expires;
131 }
132 } else {
133 if (isc_time_isepoch(&timer->idle)) {
134 due = timer->expires;
135 } else if (isc_time_isepoch(&timer->expires)) {
136 due = timer->idle;
137 } else if (isc_time_compare(&timer->idle, &timer->expires) < 0)
138 {
139 due = timer->idle;
140 } else {
141 due = timer->expires;
142 }
143 }
144
145 /*
146 * Schedule the timer.
147 */
148
149 if (timer->index > 0) {
150 /*
151 * Already scheduled.
152 */
153 cmp = isc_time_compare(&due, &timer->due);
154 timer->due = due;
155 switch (cmp) {
156 case -1:
157 isc_heap_increased(manager->heap, timer->index);
158 break;
159 case 1:
160 isc_heap_decreased(manager->heap, timer->index);
161 break;
162 case 0:
163 /* Nothing to do. */
164 break;
165 }
166 } else {
167 timer->due = due;
168 result = isc_heap_insert(manager->heap, timer);
169 if (result != ISC_R_SUCCESS) {
170 INSIST(result == ISC_R_NOMEMORY);
171 return (ISC_R_NOMEMORY);
172 }
173 manager->nscheduled++;
174 }
175
176 XTRACETIMER("schedule", timer, due);
177
178 /*
179 * If this timer is at the head of the queue, we need to ensure
180 * that we won't miss it if it has a more recent due time than
181 * the current "next" timer. We do this either by waking up the
182 * run thread, or explicitly setting the value in the manager.
183 */
184
185 if (timer->index == 1 && signal_ok) {
186 XTRACE("signal (schedule)");
187 SIGNAL(&manager->wakeup);
188 }
189
190 return (ISC_R_SUCCESS);
191 }
192
193 static inline void
194 deschedule(isc__timer_t *timer) {
195 bool need_wakeup = false;
196 isc__timermgr_t *manager;
197
198 /*
199 * The caller must ensure locking.
200 */
201
202 manager = timer->manager;
203 if (timer->index > 0) {
204 if (timer->index == 1) {
205 need_wakeup = true;
206 }
207 isc_heap_delete(manager->heap, timer->index);
208 timer->index = 0;
209 INSIST(manager->nscheduled > 0);
210 manager->nscheduled--;
211 if (need_wakeup) {
212 XTRACE("signal (deschedule)");
213 SIGNAL(&manager->wakeup);
214 }
215 }
216 }
217
218 static void
219 destroy(isc__timer_t *timer) {
220 isc__timermgr_t *manager = timer->manager;
221
222 /*
223 * The caller must ensure it is safe to destroy the timer.
224 */
225
226 LOCK(&manager->lock);
227
228 (void)isc_task_purgerange(timer->task, timer, ISC_TIMEREVENT_FIRSTEVENT,
229 ISC_TIMEREVENT_LASTEVENT, NULL);
230 deschedule(timer);
231 UNLINK(manager->timers, timer, link);
232
233 UNLOCK(&manager->lock);
234
235 isc_task_detach(&timer->task);
236 isc_mutex_destroy(&timer->lock);
237 timer->common.impmagic = 0;
238 timer->common.magic = 0;
239 isc_mem_put(manager->mctx, timer, sizeof(*timer));
240 }
241
242 isc_result_t
243 isc_timer_create(isc_timermgr_t *manager0, isc_timertype_t type,
244 const isc_time_t *expires, const isc_interval_t *interval,
245 isc_task_t *task, isc_taskaction_t action, void *arg,
246 isc_timer_t **timerp) {
247 REQUIRE(VALID_MANAGER(manager0));
248 REQUIRE(task != NULL);
249 REQUIRE(action != NULL);
250
251 isc__timermgr_t *manager;
252 isc__timer_t *timer;
253 isc_result_t result;
254 isc_time_t now;
255
256 /*
257 * Create a new 'type' timer managed by 'manager'. The timers
258 * parameters are specified by 'expires' and 'interval'. Events
259 * will be posted to 'task' and when dispatched 'action' will be
260 * called with 'arg' as the arg value. The new timer is returned
261 * in 'timerp'.
262 */
263 manager = (isc__timermgr_t *)manager0;
264 if (expires == NULL) {
265 expires = isc_time_epoch;
266 }
267 if (interval == NULL) {
268 interval = isc_interval_zero;
269 }
270 REQUIRE(type == isc_timertype_inactive ||
271 !(isc_time_isepoch(expires) && isc_interval_iszero(interval)));
272 REQUIRE(timerp != NULL && *timerp == NULL);
273 REQUIRE(type != isc_timertype_limited ||
274 !(isc_time_isepoch(expires) || isc_interval_iszero(interval)));
275
276 /*
277 * Get current time.
278 */
279 if (type != isc_timertype_inactive) {
280 TIME_NOW(&now);
281 } else {
282 /*
283 * We don't have to do this, but it keeps the compiler from
284 * complaining about "now" possibly being used without being
285 * set, even though it will never actually happen.
286 */
287 isc_time_settoepoch(&now);
288 }
289
290 timer = isc_mem_get(manager->mctx, sizeof(*timer));
291
292 timer->manager = manager;
293 isc_refcount_init(&timer->references, 1);
294
295 if (type == isc_timertype_once && !isc_interval_iszero(interval)) {
296 result = isc_time_add(&now, interval, &timer->idle);
297 if (result != ISC_R_SUCCESS) {
298 isc_mem_put(manager->mctx, timer, sizeof(*timer));
299 return (result);
300 }
301 } else {
302 isc_time_settoepoch(&timer->idle);
303 }
304
305 timer->type = type;
306 timer->expires = *expires;
307 timer->interval = *interval;
308 timer->task = NULL;
309 isc_task_attach(task, &timer->task);
310 timer->action = action;
311 /*
312 * Removing the const attribute from "arg" is the best of two
313 * evils here. If the timer->arg member is made const, then
314 * it affects a great many recipients of the timer event
315 * which did not pass in an "arg" that was truly const.
316 * Changing isc_timer_create() to not have "arg" prototyped as const,
317 * though, can cause compilers warnings for calls that *do*
318 * have a truly const arg. The caller will have to carefully
319 * keep track of whether arg started as a true const.
320 */
321 DE_CONST(arg, timer->arg);
322 timer->index = 0;
323 isc_mutex_init(&timer->lock);
324 ISC_LINK_INIT(timer, link);
325 timer->common.impmagic = TIMER_MAGIC;
326 timer->common.magic = ISCAPI_TIMER_MAGIC;
327
328 LOCK(&manager->lock);
329
330 /*
331 * Note we don't have to lock the timer like we normally would because
332 * there are no external references to it yet.
333 */
334
335 if (type != isc_timertype_inactive) {
336 result = schedule(timer, &now, true);
337 } else {
338 result = ISC_R_SUCCESS;
339 }
340 if (result == ISC_R_SUCCESS) {
341 *timerp = (isc_timer_t *)timer;
342 APPEND(manager->timers, timer, link);
343 }
344
345 UNLOCK(&manager->lock);
346
347 if (result != ISC_R_SUCCESS) {
348 timer->common.impmagic = 0;
349 timer->common.magic = 0;
350 isc_mutex_destroy(&timer->lock);
351 isc_task_detach(&timer->task);
352 isc_mem_put(manager->mctx, timer, sizeof(*timer));
353 return (result);
354 }
355
356 return (ISC_R_SUCCESS);
357 }
358
359 isc_result_t
360 isc_timer_reset(isc_timer_t *timer0, isc_timertype_t type,
361 const isc_time_t *expires, const isc_interval_t *interval,
362 bool purge) {
363 isc__timer_t *timer;
364 isc_time_t now;
365 isc__timermgr_t *manager;
366 isc_result_t result;
367
368 /*
369 * Change the timer's type, expires, and interval values to the given
370 * values. If 'purge' is true, any pending events from this timer
371 * are purged from its task's event queue.
372 */
373
374 REQUIRE(VALID_TIMER(timer0));
375 timer = (isc__timer_t *)timer0;
376 manager = timer->manager;
377 REQUIRE(VALID_MANAGER(manager));
378
379 if (expires == NULL) {
380 expires = isc_time_epoch;
381 }
382 if (interval == NULL) {
383 interval = isc_interval_zero;
384 }
385 REQUIRE(type == isc_timertype_inactive ||
386 !(isc_time_isepoch(expires) && isc_interval_iszero(interval)));
387 REQUIRE(type != isc_timertype_limited ||
388 !(isc_time_isepoch(expires) || isc_interval_iszero(interval)));
389
390 /*
391 * Get current time.
392 */
393 if (type != isc_timertype_inactive) {
394 TIME_NOW(&now);
395 } else {
396 /*
397 * We don't have to do this, but it keeps the compiler from
398 * complaining about "now" possibly being used without being
399 * set, even though it will never actually happen.
400 */
401 isc_time_settoepoch(&now);
402 }
403
404 LOCK(&manager->lock);
405 LOCK(&timer->lock);
406
407 if (purge) {
408 (void)isc_task_purgerange(timer->task, timer,
409 ISC_TIMEREVENT_FIRSTEVENT,
410 ISC_TIMEREVENT_LASTEVENT, NULL);
411 }
412 timer->type = type;
413 timer->expires = *expires;
414 timer->interval = *interval;
415 if (type == isc_timertype_once && !isc_interval_iszero(interval)) {
416 result = isc_time_add(&now, interval, &timer->idle);
417 } else {
418 isc_time_settoepoch(&timer->idle);
419 result = ISC_R_SUCCESS;
420 }
421
422 if (result == ISC_R_SUCCESS) {
423 if (type == isc_timertype_inactive) {
424 deschedule(timer);
425 result = ISC_R_SUCCESS;
426 } else {
427 result = schedule(timer, &now, true);
428 }
429 }
430
431 UNLOCK(&timer->lock);
432 UNLOCK(&manager->lock);
433
434 return (result);
435 }
436
437 isc_timertype_t
438 isc_timer_gettype(isc_timer_t *timer0) {
439 isc__timer_t *timer;
440 isc_timertype_t t;
441
442 REQUIRE(VALID_TIMER(timer0));
443 timer = (isc__timer_t *)timer0;
444
445 LOCK(&timer->lock);
446 t = timer->type;
447 UNLOCK(&timer->lock);
448
449 return (t);
450 }
451
452 isc_result_t
453 isc_timer_touch(isc_timer_t *timer0) {
454 isc__timer_t *timer;
455 isc_result_t result;
456 isc_time_t now;
457
458 /*
459 * Set the last-touched time of 'timer' to the current time.
460 */
461
462 REQUIRE(VALID_TIMER(timer0));
463 timer = (isc__timer_t *)timer0;
464
465 LOCK(&timer->lock);
466
467 /*
468 * We'd like to
469 *
470 * REQUIRE(timer->type == isc_timertype_once);
471 *
472 * but we cannot without locking the manager lock too, which we
473 * don't want to do.
474 */
475
476 TIME_NOW(&now);
477 result = isc_time_add(&now, &timer->interval, &timer->idle);
478
479 UNLOCK(&timer->lock);
480
481 return (result);
482 }
483
484 void
485 isc_timer_attach(isc_timer_t *timer0, isc_timer_t **timerp) {
486 isc__timer_t *timer;
487
488 /*
489 * Attach *timerp to timer.
490 */
491
492 REQUIRE(VALID_TIMER(timer0));
493 timer = (isc__timer_t *)timer0;
494 REQUIRE(timerp != NULL && *timerp == NULL);
495 isc_refcount_increment(&timer->references);
496
497 *timerp = (isc_timer_t *)timer;
498 }
499
500 void
501 isc_timer_detach(isc_timer_t **timerp) {
502 isc__timer_t *timer;
503
504 /*
505 * Detach *timerp from its timer.
506 */
507
508 REQUIRE(timerp != NULL);
509 timer = (isc__timer_t *)*timerp;
510 REQUIRE(VALID_TIMER(timer));
511
512 if (isc_refcount_decrement(&timer->references) == 1) {
513 destroy(timer);
514 }
515
516 *timerp = NULL;
517 }
518
519 static void
520 dispatch(isc__timermgr_t *manager, isc_time_t *now) {
521 bool done = false, post_event, need_schedule;
522 isc_timerevent_t *event;
523 isc_eventtype_t type = 0;
524 isc__timer_t *timer;
525 isc_result_t result;
526 bool idle;
527
528 /*!
529 * The caller must be holding the manager lock.
530 */
531
532 while (manager->nscheduled > 0 && !done) {
533 timer = isc_heap_element(manager->heap, 1);
534 INSIST(timer != NULL && timer->type != isc_timertype_inactive);
535 if (isc_time_compare(now, &timer->due) >= 0) {
536 if (timer->type == isc_timertype_ticker) {
537 type = ISC_TIMEREVENT_TICK;
538 post_event = true;
539 need_schedule = true;
540 } else if (timer->type == isc_timertype_limited) {
541 int cmp;
542 cmp = isc_time_compare(now, &timer->expires);
543 if (cmp >= 0) {
544 type = ISC_TIMEREVENT_LIFE;
545 post_event = true;
546 need_schedule = false;
547 } else {
548 type = ISC_TIMEREVENT_TICK;
549 post_event = true;
550 need_schedule = true;
551 }
552 } else if (!isc_time_isepoch(&timer->expires) &&
553 isc_time_compare(now, &timer->expires) >= 0)
554 {
555 type = ISC_TIMEREVENT_LIFE;
556 post_event = true;
557 need_schedule = false;
558 } else {
559 idle = false;
560
561 LOCK(&timer->lock);
562 if (!isc_time_isepoch(&timer->idle) &&
563 isc_time_compare(now, &timer->idle) >= 0) {
564 idle = true;
565 }
566 UNLOCK(&timer->lock);
567 if (idle) {
568 type = ISC_TIMEREVENT_IDLE;
569 post_event = true;
570 need_schedule = false;
571 } else {
572 /*
573 * Idle timer has been touched;
574 * reschedule.
575 */
576 XTRACEID("idle reschedule", timer);
577 post_event = false;
578 need_schedule = true;
579 }
580 }
581
582 if (post_event) {
583 XTRACEID("posting", timer);
584 /*
585 * XXX We could preallocate this event.
586 */
587 event = (isc_timerevent_t *)isc_event_allocate(
588 manager->mctx, timer, type,
589 timer->action, timer->arg,
590 sizeof(*event));
591
592 if (event != NULL) {
593 event->due = timer->due;
594 isc_task_send(timer->task,
595 ISC_EVENT_PTR(&event));
596 } else {
597 UNEXPECTED_ERROR(__FILE__, __LINE__,
598 "%s",
599 "couldn't allocate "
600 "event");
601 }
602 }
603
604 timer->index = 0;
605 isc_heap_delete(manager->heap, 1);
606 manager->nscheduled--;
607
608 if (need_schedule) {
609 result = schedule(timer, now, false);
610 if (result != ISC_R_SUCCESS) {
611 UNEXPECTED_ERROR(__FILE__, __LINE__,
612 "%s: %u",
613 "couldn't schedule "
614 "timer",
615 result);
616 }
617 }
618 } else {
619 manager->due = timer->due;
620 done = true;
621 }
622 }
623 }
624
625 static isc_threadresult_t
626 #ifdef _WIN32 /* XXXDCL */
627 WINAPI
628 #endif /* ifdef _WIN32 */
629 run(void *uap) {
630 isc__timermgr_t *manager = uap;
631 isc_time_t now;
632 isc_result_t result;
633
634 LOCK(&manager->lock);
635 while (!manager->done) {
636 TIME_NOW(&now);
637
638 XTRACETIME("running", now);
639
640 dispatch(manager, &now);
641
642 if (manager->nscheduled > 0) {
643 XTRACETIME2("waituntil", manager->due, now);
644 result = WAITUNTIL(&manager->wakeup, &manager->lock,
645 &manager->due);
646 INSIST(result == ISC_R_SUCCESS ||
647 result == ISC_R_TIMEDOUT);
648 } else {
649 XTRACETIME("wait", now);
650 WAIT(&manager->wakeup, &manager->lock);
651 }
652 XTRACE("wakeup");
653 }
654 UNLOCK(&manager->lock);
655
656 #ifdef OPENSSL_LEAKS
657 ERR_remove_state(0);
658 #endif /* ifdef OPENSSL_LEAKS */
659
660 return ((isc_threadresult_t)0);
661 }
662
663 static bool
664 sooner(void *v1, void *v2) {
665 isc__timer_t *t1, *t2;
666
667 t1 = v1;
668 t2 = v2;
669 REQUIRE(VALID_TIMER(t1));
670 REQUIRE(VALID_TIMER(t2));
671
672 if (isc_time_compare(&t1->due, &t2->due) < 0) {
673 return (true);
674 }
675 return (false);
676 }
677
678 static void
679 set_index(void *what, unsigned int index) {
680 isc__timer_t *timer;
681
682 REQUIRE(VALID_TIMER(what));
683 timer = what;
684
685 timer->index = index;
686 }
687
688 isc_result_t
689 isc_timermgr_create(isc_mem_t *mctx, isc_timermgr_t **managerp) {
690 isc__timermgr_t *manager;
691 isc_result_t result;
692
693 /*
694 * Create a timer manager.
695 */
696
697 REQUIRE(managerp != NULL && *managerp == NULL);
698
699 manager = isc_mem_get(mctx, sizeof(*manager));
700
701 manager->common.impmagic = TIMER_MANAGER_MAGIC;
702 manager->common.magic = ISCAPI_TIMERMGR_MAGIC;
703 manager->mctx = NULL;
704 manager->done = false;
705 INIT_LIST(manager->timers);
706 manager->nscheduled = 0;
707 isc_time_settoepoch(&manager->due);
708 manager->heap = NULL;
709 result = isc_heap_create(mctx, sooner, set_index, 0, &manager->heap);
710 if (result != ISC_R_SUCCESS) {
711 INSIST(result == ISC_R_NOMEMORY);
712 isc_mem_put(mctx, manager, sizeof(*manager));
713 return (ISC_R_NOMEMORY);
714 }
715 isc_mutex_init(&manager->lock);
716 isc_mem_attach(mctx, &manager->mctx);
717 isc_condition_init(&manager->wakeup);
718 isc_thread_create(run, manager, &manager->thread);
719 isc_thread_setname(manager->thread, "isc-timer");
720
721 *managerp = (isc_timermgr_t *)manager;
722
723 return (ISC_R_SUCCESS);
724 }
725
726 void
727 isc_timermgr_poke(isc_timermgr_t *manager0) {
728 isc__timermgr_t *manager;
729
730 REQUIRE(VALID_MANAGER(manager0));
731 manager = (isc__timermgr_t *)manager0;
732
733 SIGNAL(&manager->wakeup);
734 }
735
736 void
737 isc_timermgr_destroy(isc_timermgr_t **managerp) {
738 isc__timermgr_t *manager;
739
740 /*
741 * Destroy a timer manager.
742 */
743
744 REQUIRE(managerp != NULL);
745 manager = (isc__timermgr_t *)*managerp;
746 REQUIRE(VALID_MANAGER(manager));
747
748 LOCK(&manager->lock);
749
750 REQUIRE(EMPTY(manager->timers));
751 manager->done = true;
752
753 XTRACE("signal (destroy)");
754 SIGNAL(&manager->wakeup);
755
756 UNLOCK(&manager->lock);
757
758 /*
759 * Wait for thread to exit.
760 */
761 isc_thread_join(manager->thread, NULL);
762
763 /*
764 * Clean up.
765 */
766 (void)isc_condition_destroy(&manager->wakeup);
767 isc_mutex_destroy(&manager->lock);
768 isc_heap_destroy(&manager->heap);
769 manager->common.impmagic = 0;
770 manager->common.magic = 0;
771 isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager));
772
773 *managerp = NULL;
774 }
775
776 isc_result_t
777 isc_timermgr_createinctx(isc_mem_t *mctx, isc_timermgr_t **managerp) {
778 isc_result_t result;
779
780 result = isc_timermgr_create(mctx, managerp);
781
782 return (result);
783 }
784