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