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