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