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