timer.c revision 1.1.1.8 1 /* $NetBSD: timer.c,v 1.1.1.8 2023/01/25 20:36:48 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 {
545 idle = true;
546 }
547 UNLOCK(&timer->lock);
548 if (idle) {
549 type = ISC_TIMEREVENT_IDLE;
550 post_event = true;
551 need_schedule = false;
552 } else {
553 /*
554 * Idle timer has been touched;
555 * reschedule.
556 */
557 XTRACEID("idle reschedule", timer);
558 post_event = false;
559 need_schedule = true;
560 }
561 }
562
563 if (post_event) {
564 XTRACEID("posting", timer);
565 /*
566 * XXX We could preallocate this event.
567 */
568 event = (isc_timerevent_t *)isc_event_allocate(
569 manager->mctx, timer, type,
570 timer->action, timer->arg,
571 sizeof(*event));
572
573 if (event != NULL) {
574 event->due = timer->due;
575 isc_task_send(timer->task,
576 ISC_EVENT_PTR(&event));
577 } else {
578 UNEXPECTED_ERROR(__FILE__, __LINE__,
579 "%s",
580 "couldn't allocate "
581 "event");
582 }
583 }
584
585 timer->index = 0;
586 isc_heap_delete(manager->heap, 1);
587 manager->nscheduled--;
588
589 if (need_schedule) {
590 result = schedule(timer, now, false);
591 if (result != ISC_R_SUCCESS) {
592 UNEXPECTED_ERROR(__FILE__, __LINE__,
593 "%s: %u",
594 "couldn't schedule "
595 "timer",
596 result);
597 }
598 }
599 } else {
600 manager->due = timer->due;
601 done = true;
602 }
603 }
604 }
605
606 static isc_threadresult_t
607 #ifdef _WIN32 /* XXXDCL */
608 WINAPI
609 #endif /* ifdef _WIN32 */
610 run(void *uap) {
611 isc_timermgr_t *manager = uap;
612 isc_time_t now;
613 isc_result_t result;
614
615 LOCK(&manager->lock);
616 while (!manager->done) {
617 TIME_NOW(&now);
618
619 XTRACETIME("running", now);
620
621 dispatch(manager, &now);
622
623 if (manager->nscheduled > 0) {
624 XTRACETIME2("waituntil", manager->due, now);
625 result = WAITUNTIL(&manager->wakeup, &manager->lock,
626 &manager->due);
627 INSIST(result == ISC_R_SUCCESS ||
628 result == ISC_R_TIMEDOUT);
629 } else {
630 XTRACETIME("wait", now);
631 WAIT(&manager->wakeup, &manager->lock);
632 }
633 XTRACE("wakeup");
634 }
635 UNLOCK(&manager->lock);
636
637 #ifdef OPENSSL_LEAKS
638 ERR_remove_state(0);
639 #endif /* ifdef OPENSSL_LEAKS */
640
641 return ((isc_threadresult_t)0);
642 }
643
644 static bool
645 sooner(void *v1, void *v2) {
646 isc_timer_t *t1, *t2;
647
648 t1 = v1;
649 t2 = v2;
650 REQUIRE(VALID_TIMER(t1));
651 REQUIRE(VALID_TIMER(t2));
652
653 if (isc_time_compare(&t1->due, &t2->due) < 0) {
654 return (true);
655 }
656 return (false);
657 }
658
659 static void
660 set_index(void *what, unsigned int index) {
661 isc_timer_t *timer;
662
663 REQUIRE(VALID_TIMER(what));
664 timer = what;
665
666 timer->index = index;
667 }
668
669 isc_result_t
670 isc_timermgr_create(isc_mem_t *mctx, isc_timermgr_t **managerp) {
671 isc_timermgr_t *manager;
672
673 /*
674 * Create a timer manager.
675 */
676
677 REQUIRE(managerp != NULL && *managerp == NULL);
678
679 manager = isc_mem_get(mctx, sizeof(*manager));
680
681 manager->magic = TIMER_MANAGER_MAGIC;
682 manager->mctx = NULL;
683 manager->done = false;
684 INIT_LIST(manager->timers);
685 manager->nscheduled = 0;
686 isc_time_settoepoch(&manager->due);
687 manager->heap = NULL;
688 isc_heap_create(mctx, sooner, set_index, 0, &manager->heap);
689 isc_mutex_init(&manager->lock);
690 isc_mem_attach(mctx, &manager->mctx);
691 isc_condition_init(&manager->wakeup);
692 isc_thread_create(run, manager, &manager->thread);
693 isc_thread_setname(manager->thread, "isc-timer");
694
695 *managerp = manager;
696
697 return (ISC_R_SUCCESS);
698 }
699
700 void
701 isc_timermgr_poke(isc_timermgr_t *manager) {
702 REQUIRE(VALID_MANAGER(manager));
703
704 SIGNAL(&manager->wakeup);
705 }
706
707 void
708 isc_timermgr_destroy(isc_timermgr_t **managerp) {
709 isc_timermgr_t *manager;
710
711 /*
712 * Destroy a timer manager.
713 */
714
715 REQUIRE(managerp != NULL);
716 manager = *managerp;
717 REQUIRE(VALID_MANAGER(manager));
718
719 LOCK(&manager->lock);
720
721 REQUIRE(EMPTY(manager->timers));
722 manager->done = true;
723
724 XTRACE("signal (destroy)");
725 SIGNAL(&manager->wakeup);
726
727 UNLOCK(&manager->lock);
728
729 /*
730 * Wait for thread to exit.
731 */
732 isc_thread_join(manager->thread, NULL);
733
734 /*
735 * Clean up.
736 */
737 (void)isc_condition_destroy(&manager->wakeup);
738 isc_mutex_destroy(&manager->lock);
739 isc_heap_destroy(&manager->heap);
740 manager->magic = 0;
741 isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager));
742
743 *managerp = NULL;
744 }
745