xref: /openbsd-src/usr.bin/dig/lib/isc/timer.c (revision 75fe954b696c78c4f41aa6e796f97f0bff767a8e)
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.9 2020/02/16 08:05:41 florian Exp $ */
18 
19 /*! \file */
20 
21 
22 #include <stdlib.h>
23 #include <isc/heap.h>
24 #include <isc/magic.h>
25 #include <isc/task.h>
26 #include <isc/time.h>
27 #include <isc/timer.h>
28 #include <isc/util.h>
29 
30 #include "timer_p.h"
31 
32 #define TIMER_MAGIC			ISC_MAGIC('T', 'I', 'M', 'R')
33 #define VALID_TIMER(t)			ISC_MAGIC_VALID(t, TIMER_MAGIC)
34 
35 typedef struct isc__timer isc__timer_t;
36 typedef struct isc__timermgr isc__timermgr_t;
37 
38 struct isc__timer {
39 	/*! Not locked. */
40 	isc_timer_t			common;
41 	isc__timermgr_t *		manager;
42 	/*! Locked by timer lock. */
43 	unsigned int			references;
44 	isc_time_t			idle;
45 	/*! Locked by manager lock. */
46 	isc_timertype_t			type;
47 	isc_time_t			expires;
48 	interval_t			interval;
49 	isc_task_t *			task;
50 	isc_taskaction_t		action;
51 	void *				arg;
52 	unsigned int			index;
53 	isc_time_t			due;
54 	LINK(isc__timer_t)		link;
55 };
56 
57 #define TIMER_MANAGER_MAGIC		ISC_MAGIC('T', 'I', 'M', 'M')
58 #define VALID_MANAGER(m)		ISC_MAGIC_VALID(m, TIMER_MANAGER_MAGIC)
59 
60 struct isc__timermgr {
61 	/* Not locked. */
62 	isc_timermgr_t			common;
63 	/* Locked by manager lock. */
64 	isc_boolean_t			done;
65 	LIST(isc__timer_t)		timers;
66 	unsigned int			nscheduled;
67 	isc_time_t			due;
68 	unsigned int			refs;
69 	isc_heap_t *			heap;
70 };
71 
72 /*%
73  * The following are intended for internal use (indicated by "isc__"
74  * prefix) but are not declared as static, allowing direct access from
75  * unit tests etc.
76  */
77 
78 isc_result_t
79 isc__timer_create(isc_timermgr_t *manager, isc_timertype_t type,
80 		  const isc_time_t *expires, const interval_t *interval,
81 		  isc_task_t *task, isc_taskaction_t action, void *arg,
82 		  isc_timer_t **timerp);
83 isc_result_t
84 isc__timer_reset(isc_timer_t *timer, isc_timertype_t type,
85 		 const isc_time_t *expires, const interval_t *interval,
86 		 isc_boolean_t purge);
87 isc_timertype_t
88 isc_timer_gettype(isc_timer_t *timer);
89 isc_result_t
90 isc__timer_touch(isc_timer_t *timer);
91 void
92 isc__timer_attach(isc_timer_t *timer0, isc_timer_t **timerp);
93 void
94 isc__timer_detach(isc_timer_t **timerp);
95 isc_result_t
96 isc__timermgr_create(isc_timermgr_t **managerp);
97 void
98 isc__timermgr_destroy(isc_timermgr_t **managerp);
99 
100 /*!
101  * If the manager is supposed to be shared, there can be only one.
102  */
103 static isc__timermgr_t *timermgr = NULL;
104 
105 static inline isc_result_t
106 schedule(isc__timer_t *timer, isc_time_t *now, isc_boolean_t signal_ok) {
107 	isc_result_t result;
108 	isc__timermgr_t *manager;
109 	isc_time_t due;
110 	int cmp;
111 
112 	/*!
113 	 * Note: the caller must ensure locking.
114 	 */
115 
116 	UNUSED(signal_ok);
117 
118 	manager = timer->manager;
119 
120 	/*
121 	 * Compute the new due time.
122 	 */
123 	if (isc_time_isepoch(&timer->idle))
124 		due = timer->expires;
125 	else if (isc_time_isepoch(&timer->expires))
126 		due = timer->idle;
127 	else if (isc_time_compare(&timer->idle, &timer->expires) < 0)
128 		due = timer->idle;
129 	else
130 		due = timer->expires;
131 
132 	/*
133 	 * Schedule the timer.
134 	 */
135 
136 	if (timer->index > 0) {
137 		/*
138 		 * Already scheduled.
139 		 */
140 		cmp = isc_time_compare(&due, &timer->due);
141 		timer->due = due;
142 		switch (cmp) {
143 		case -1:
144 			isc_heap_increased(manager->heap, timer->index);
145 			break;
146 		case 1:
147 			isc_heap_decreased(manager->heap, timer->index);
148 			break;
149 		case 0:
150 			/* Nothing to do. */
151 			break;
152 		}
153 	} else {
154 		timer->due = due;
155 		result = isc_heap_insert(manager->heap, timer);
156 		if (result != ISC_R_SUCCESS) {
157 			INSIST(result == ISC_R_NOMEMORY);
158 			return (ISC_R_NOMEMORY);
159 		}
160 		manager->nscheduled++;
161 	}
162 
163 	/*
164 	 * If this timer is at the head of the queue, we need to ensure
165 	 * that we won't miss it if it has a more recent due time than
166 	 * the current "next" timer.  We do this either by waking up the
167 	 * run thread, or explicitly setting the value in the manager.
168 	 */
169 	if (timer->index == 1 &&
170 	    isc_time_compare(&timer->due, &manager->due) < 0)
171 		manager->due = timer->due;
172 
173 	return (ISC_R_SUCCESS);
174 }
175 
176 static inline void
177 deschedule(isc__timer_t *timer) {
178 	isc__timermgr_t *manager;
179 
180 	/*
181 	 * The caller must ensure locking.
182 	 */
183 
184 	manager = timer->manager;
185 	if (timer->index > 0) {
186 		isc_heap_delete(manager->heap, timer->index);
187 		timer->index = 0;
188 		INSIST(manager->nscheduled > 0);
189 		manager->nscheduled--;
190 	}
191 }
192 
193 static void
194 destroy(isc__timer_t *timer) {
195 	isc__timermgr_t *manager = timer->manager;
196 
197 	/*
198 	 * The caller must ensure it is safe to destroy the timer.
199 	 */
200 
201 	(void)isc_task_purgerange(timer->task,
202 				  timer,
203 				  ISC_TIMEREVENT_FIRSTEVENT,
204 				  ISC_TIMEREVENT_LASTEVENT,
205 				  NULL);
206 	deschedule(timer);
207 	UNLINK(manager->timers, timer, link);
208 
209 	isc_task_detach(&timer->task);
210 	timer->common.impmagic = 0;
211 	timer->common.magic = 0;
212 	free(timer);
213 }
214 
215 isc_result_t
216 isc__timer_create(isc_timermgr_t *manager0, isc_timertype_t type,
217 		  const isc_time_t *expires, const interval_t *interval,
218 		  isc_task_t *task, isc_taskaction_t action, void *arg,
219 		  isc_timer_t **timerp)
220 {
221 	isc__timermgr_t *manager = (isc__timermgr_t *)manager0;
222 	isc__timer_t *timer;
223 	isc_result_t result;
224 	isc_time_t now;
225 
226 	/*
227 	 * Create a new 'type' timer managed by 'manager'.  The timers
228 	 * parameters are specified by 'expires' and 'interval'.  Events
229 	 * will be posted to 'task' and when dispatched 'action' will be
230 	 * called with 'arg' as the arg value.  The new timer is returned
231 	 * in 'timerp'.
232 	 */
233 
234 	REQUIRE(VALID_MANAGER(manager));
235 	REQUIRE(task != NULL);
236 	REQUIRE(action != NULL);
237 	if (expires == NULL)
238 		expires = isc_time_epoch;
239 	if (interval == NULL)
240 		interval = interval_zero;
241 	REQUIRE(!(isc_time_isepoch(expires) && interval_iszero(interval)));
242 	REQUIRE(timerp != NULL && *timerp == NULL);
243 	REQUIRE(!(isc_time_isepoch(expires) || interval_iszero(interval)));
244 
245 	/*
246 	 * Get current time.
247 	 */
248 	TIME_NOW(&now);
249 
250 	timer = malloc(sizeof(*timer));
251 	if (timer == NULL)
252 		return (ISC_R_NOMEMORY);
253 
254 	timer->manager = manager;
255 	timer->references = 1;
256 
257 	if (type == isc_timertype_once && !interval_iszero(interval)) {
258 		result = isc_time_add(&now, interval, &timer->idle);
259 		if (result != ISC_R_SUCCESS) {
260 			free(timer);
261 			return (result);
262 		}
263 	}
264 
265 	timer->type = type;
266 	timer->expires = *expires;
267 	timer->interval = *interval;
268 	timer->task = NULL;
269 	isc_task_attach(task, &timer->task);
270 	timer->action = action;
271 	/*
272 	 * Removing the const attribute from "arg" is the best of two
273 	 * evils here.  If the timer->arg member is made const, then
274 	 * it affects a great many recipients of the timer event
275 	 * which did not pass in an "arg" that was truly const.
276 	 * Changing isc_timer_create() to not have "arg" prototyped as const,
277 	 * though, can cause compilers warnings for calls that *do*
278 	 * have a truly const arg.  The caller will have to carefully
279 	 * keep track of whether arg started as a true const.
280 	 */
281 	DE_CONST(arg, timer->arg);
282 	timer->index = 0;
283 	ISC_LINK_INIT(timer, link);
284 	timer->common.impmagic = TIMER_MAGIC;
285 	timer->common.magic = ISCAPI_TIMER_MAGIC;
286 
287 	result = schedule(timer, &now, ISC_TRUE);
288 	if (result == ISC_R_SUCCESS)
289 		APPEND(manager->timers, timer, link);
290 
291 	if (result != ISC_R_SUCCESS) {
292 		timer->common.impmagic = 0;
293 		timer->common.magic = 0;
294 		isc_task_detach(&timer->task);
295 		free(timer);
296 		return (result);
297 	}
298 
299 	*timerp = (isc_timer_t *)timer;
300 
301 	return (ISC_R_SUCCESS);
302 }
303 
304 isc_result_t
305 isc__timer_reset(isc_timer_t *timer0, isc_timertype_t type,
306 		 const isc_time_t *expires, const interval_t *interval,
307 		 isc_boolean_t purge)
308 {
309 	isc__timer_t *timer = (isc__timer_t *)timer0;
310 	isc_time_t now;
311 	isc__timermgr_t *manager;
312 	isc_result_t result;
313 
314 	/*
315 	 * Change the timer's type, expires, and interval values to the given
316 	 * values.  If 'purge' is ISC_TRUE, any pending events from this timer
317 	 * are purged from its task's event queue.
318 	 */
319 
320 	REQUIRE(VALID_TIMER(timer));
321 	manager = timer->manager;
322 	REQUIRE(VALID_MANAGER(manager));
323 
324 	if (expires == NULL)
325 		expires = isc_time_epoch;
326 	if (interval == NULL)
327 		interval = interval_zero;
328 	REQUIRE(!(isc_time_isepoch(expires) && interval_iszero(interval)));
329 	REQUIRE(!(isc_time_isepoch(expires) || interval_iszero(interval)));
330 
331 	/*
332 	 * Get current time.
333 	 */
334 	TIME_NOW(&now);
335 
336 	if (purge)
337 		(void)isc_task_purgerange(timer->task,
338 					  timer,
339 					  ISC_TIMEREVENT_FIRSTEVENT,
340 					  ISC_TIMEREVENT_LASTEVENT,
341 					  NULL);
342 	timer->type = type;
343 	timer->expires = *expires;
344 	timer->interval = *interval;
345 	if (type == isc_timertype_once && !interval_iszero(interval)) {
346 		result = isc_time_add(&now, interval, &timer->idle);
347 	} else {
348 		isc_time_settoepoch(&timer->idle);
349 		result = ISC_R_SUCCESS;
350 	}
351 
352 	if (result == ISC_R_SUCCESS) {
353 		result = schedule(timer, &now, ISC_TRUE);
354 	}
355 
356 	return (result);
357 }
358 
359 isc_timertype_t
360 isc_timer_gettype(isc_timer_t *timer0) {
361 	isc__timer_t *timer = (isc__timer_t *)timer0;
362 	isc_timertype_t t;
363 
364 	REQUIRE(VALID_TIMER(timer));
365 
366 	t = timer->type;
367 
368 	return (t);
369 }
370 
371 isc_result_t
372 isc__timer_touch(isc_timer_t *timer0) {
373 	isc__timer_t *timer = (isc__timer_t *)timer0;
374 	isc_result_t result;
375 	isc_time_t now;
376 
377 	/*
378 	 * Set the last-touched time of 'timer' to the current time.
379 	 */
380 
381 	REQUIRE(VALID_TIMER(timer));
382 
383 	TIME_NOW(&now);
384 	result = isc_time_add(&now, &timer->interval, &timer->idle);
385 
386 	return (result);
387 }
388 
389 void
390 isc__timer_attach(isc_timer_t *timer0, isc_timer_t **timerp) {
391 	isc__timer_t *timer = (isc__timer_t *)timer0;
392 
393 	/*
394 	 * Attach *timerp to timer.
395 	 */
396 
397 	REQUIRE(VALID_TIMER(timer));
398 	REQUIRE(timerp != NULL && *timerp == NULL);
399 
400 	timer->references++;
401 
402 	*timerp = (isc_timer_t *)timer;
403 }
404 
405 void
406 isc__timer_detach(isc_timer_t **timerp) {
407 	isc__timer_t *timer;
408 	isc_boolean_t free_timer = ISC_FALSE;
409 
410 	/*
411 	 * Detach *timerp from its timer.
412 	 */
413 
414 	REQUIRE(timerp != NULL);
415 	timer = (isc__timer_t *)*timerp;
416 	REQUIRE(VALID_TIMER(timer));
417 
418 	REQUIRE(timer->references > 0);
419 	timer->references--;
420 	if (timer->references == 0)
421 		free_timer = ISC_TRUE;
422 
423 	if (free_timer)
424 		destroy(timer);
425 
426 	*timerp = NULL;
427 }
428 
429 static void
430 dispatch(isc__timermgr_t *manager, isc_time_t *now) {
431 	isc_boolean_t done = ISC_FALSE, post_event, need_schedule;
432 	isc_timerevent_t *event;
433 	isc_eventtype_t type = 0;
434 	isc__timer_t *timer;
435 	isc_result_t result;
436 	isc_boolean_t idle;
437 
438 	/*!
439 	 * The caller must be holding the manager lock.
440 	 */
441 
442 	while (manager->nscheduled > 0 && !done) {
443 		timer = isc_heap_element(manager->heap, 1);
444 		INSIST(timer != NULL);
445 		if (isc_time_compare(now, &timer->due) >= 0) {
446 			if (!isc_time_isepoch(&timer->expires) &&
447 				   isc_time_compare(now,
448 						    &timer->expires) >= 0) {
449 				type = ISC_TIMEREVENT_LIFE;
450 				post_event = ISC_TRUE;
451 				need_schedule = ISC_FALSE;
452 			} else {
453 				idle = ISC_FALSE;
454 
455 				if (!isc_time_isepoch(&timer->idle) &&
456 				    isc_time_compare(now,
457 						     &timer->idle) >= 0) {
458 					idle = ISC_TRUE;
459 				}
460 				if (idle) {
461 					type = ISC_TIMEREVENT_IDLE;
462 					post_event = ISC_TRUE;
463 					need_schedule = ISC_FALSE;
464 				} else {
465 					/*
466 					 * Idle timer has been touched;
467 					 * reschedule.
468 					 */
469 					post_event = ISC_FALSE;
470 					need_schedule = ISC_TRUE;
471 				}
472 			}
473 
474 			if (post_event) {
475 				/*
476 				 * XXX We could preallocate this event.
477 				 */
478 				event = (isc_timerevent_t *)isc_event_allocate(
479 							   timer,
480 							   type,
481 							   timer->action,
482 							   timer->arg,
483 							   sizeof(*event));
484 
485 				if (event != NULL) {
486 					event->due = timer->due;
487 					isc_task_send(timer->task,
488 						      ISC_EVENT_PTR(&event));
489 				} else
490 					UNEXPECTED_ERROR(__FILE__, __LINE__, "%s",
491 						 "couldn't allocate event");
492 			}
493 
494 			timer->index = 0;
495 			isc_heap_delete(manager->heap, 1);
496 			manager->nscheduled--;
497 
498 			if (need_schedule) {
499 				result = schedule(timer, now, ISC_FALSE);
500 				if (result != ISC_R_SUCCESS)
501 					UNEXPECTED_ERROR(__FILE__, __LINE__,
502 						"%s: %u",
503 						"couldn't schedule timer",
504 						result);
505 			}
506 		} else {
507 			manager->due = timer->due;
508 			done = ISC_TRUE;
509 		}
510 	}
511 }
512 
513 static isc_boolean_t
514 sooner(void *v1, void *v2) {
515 	isc__timer_t *t1, *t2;
516 
517 	t1 = v1;
518 	t2 = v2;
519 	REQUIRE(VALID_TIMER(t1));
520 	REQUIRE(VALID_TIMER(t2));
521 
522 	if (isc_time_compare(&t1->due, &t2->due) < 0)
523 		return (ISC_TRUE);
524 	return (ISC_FALSE);
525 }
526 
527 static void
528 set_index(void *what, unsigned int index) {
529 	isc__timer_t *timer;
530 
531 	timer = what;
532 	REQUIRE(VALID_TIMER(timer));
533 
534 	timer->index = index;
535 }
536 
537 isc_result_t
538 isc__timermgr_create(isc_timermgr_t **managerp) {
539 	isc__timermgr_t *manager;
540 	isc_result_t result;
541 
542 	/*
543 	 * Create a timer manager.
544 	 */
545 
546 	REQUIRE(managerp != NULL && *managerp == NULL);
547 
548 	if (timermgr != NULL) {
549 		timermgr->refs++;
550 		*managerp = (isc_timermgr_t *)timermgr;
551 		return (ISC_R_SUCCESS);
552 	}
553 
554 	manager = malloc(sizeof(*manager));
555 	if (manager == NULL)
556 		return (ISC_R_NOMEMORY);
557 
558 	manager->common.impmagic = TIMER_MANAGER_MAGIC;
559 	manager->common.magic = ISCAPI_TIMERMGR_MAGIC;
560 	manager->done = ISC_FALSE;
561 	INIT_LIST(manager->timers);
562 	manager->nscheduled = 0;
563 	isc_time_settoepoch(&manager->due);
564 	manager->heap = NULL;
565 	result = isc_heap_create(sooner, set_index, 0, &manager->heap);
566 	if (result != ISC_R_SUCCESS) {
567 		INSIST(result == ISC_R_NOMEMORY);
568 		free(manager);
569 		return (ISC_R_NOMEMORY);
570 	}
571 	manager->refs = 1;
572 	timermgr = manager;
573 
574 	*managerp = (isc_timermgr_t *)manager;
575 
576 	return (ISC_R_SUCCESS);
577 }
578 
579 void
580 isc__timermgr_destroy(isc_timermgr_t **managerp) {
581 	isc__timermgr_t *manager;
582 
583 	/*
584 	 * Destroy a timer manager.
585 	 */
586 
587 	REQUIRE(managerp != NULL);
588 	manager = (isc__timermgr_t *)*managerp;
589 	REQUIRE(VALID_MANAGER(manager));
590 
591 	manager->refs--;
592 	if (manager->refs > 0) {
593 		*managerp = NULL;
594 		return;
595 	}
596 	timermgr = NULL;
597 
598 	isc__timermgr_dispatch((isc_timermgr_t *)manager);
599 
600 	REQUIRE(EMPTY(manager->timers));
601 	manager->done = ISC_TRUE;
602 
603 	/*
604 	 * Clean up.
605 	 */
606 	isc_heap_destroy(&manager->heap);
607 	manager->common.impmagic = 0;
608 	manager->common.magic = 0;
609 	free(manager);
610 
611 	*managerp = NULL;
612 
613 	timermgr = NULL;
614 }
615 
616 isc_result_t
617 isc__timermgr_nextevent(isc_timermgr_t *manager0, isc_time_t *when) {
618 	isc__timermgr_t *manager = (isc__timermgr_t *)manager0;
619 
620 	if (manager == NULL)
621 		manager = timermgr;
622 	if (manager == NULL || manager->nscheduled == 0)
623 		return (ISC_R_NOTFOUND);
624 	*when = manager->due;
625 	return (ISC_R_SUCCESS);
626 }
627 
628 void
629 isc__timermgr_dispatch(isc_timermgr_t *manager0) {
630 	isc__timermgr_t *manager = (isc__timermgr_t *)manager0;
631 	isc_time_t now;
632 
633 	if (manager == NULL)
634 		manager = timermgr;
635 	if (manager == NULL)
636 		return;
637 	TIME_NOW(&now);
638 	dispatch(manager, &now);
639 }
640 
641 isc_result_t
642 isc_timermgr_create(isc_timermgr_t **managerp) {
643 	return (isc__timermgr_create(managerp));
644 }
645 
646 void
647 isc_timermgr_destroy(isc_timermgr_t **managerp) {
648 	REQUIRE(*managerp != NULL && ISCAPI_TIMERMGR_VALID(*managerp));
649 
650 	isc__timermgr_destroy(managerp);
651 
652 	ENSURE(*managerp == NULL);
653 }
654 
655 isc_result_t
656 isc_timer_create(isc_timermgr_t *manager, isc_timertype_t type,
657 		 const isc_time_t *expires, const interval_t *interval,
658 		 isc_task_t *task, isc_taskaction_t action, void *arg,
659 		 isc_timer_t **timerp)
660 {
661 	REQUIRE(ISCAPI_TIMERMGR_VALID(manager));
662 
663 	return (isc__timer_create(manager, type, expires, interval,
664 				  task, action, arg, timerp));
665 }
666 
667 void
668 isc_timer_detach(isc_timer_t **timerp) {
669 	REQUIRE(timerp != NULL && ISCAPI_TIMER_VALID(*timerp));
670 
671 	isc__timer_detach(timerp);
672 
673 	ENSURE(*timerp == NULL);
674 }
675 
676 isc_result_t
677 isc_timer_reset(isc_timer_t *timer, isc_timertype_t type,
678 		const isc_time_t *expires, const interval_t *interval,
679 		isc_boolean_t purge)
680 {
681 	REQUIRE(ISCAPI_TIMER_VALID(timer));
682 
683 	return (isc__timer_reset(timer, type, expires,
684 				 interval, purge));
685 }
686 
687 isc_result_t
688 isc_timer_touch(isc_timer_t *timer) {
689 	REQUIRE(ISCAPI_TIMER_VALID(timer));
690 
691 	return (isc__timer_touch(timer));
692 }
693