xref: /openbsd-src/usr.bin/dig/lib/isc/timer.c (revision a12dcefb48e200107d0d2d0ced8c8d9c25cb6d86)
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.18 2020/02/16 21:11:02 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 void
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 	clock_gettime(CLOCK_REALTIME, &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 		timespecadd(&now, interval, &timer->idle);
232 
233 	timer->interval = *interval;
234 	timer->task = NULL;
235 	isc_task_attach(task, &timer->task);
236 	timer->action = action;
237 	/*
238 	 * Removing the const attribute from "arg" is the best of two
239 	 * evils here.  If the timer->arg member is made const, then
240 	 * it affects a great many recipients of the timer event
241 	 * which did not pass in an "arg" that was truly const.
242 	 * Changing isc_timer_create() to not have "arg" prototyped as const,
243 	 * though, can cause compilers warnings for calls that *do*
244 	 * have a truly const arg.  The caller will have to carefully
245 	 * keep track of whether arg started as a true const.
246 	 */
247 	DE_CONST(arg, timer->arg);
248 	timer->index = 0;
249 	ISC_LINK_INIT(timer, link);
250 	timer->common.impmagic = TIMER_MAGIC;
251 	timer->common.magic = ISCAPI_TIMER_MAGIC;
252 
253 	result = schedule(timer, &now, ISC_TRUE);
254 	if (result == ISC_R_SUCCESS)
255 		APPEND(manager->timers, timer, link);
256 
257 	if (result != ISC_R_SUCCESS) {
258 		timer->common.impmagic = 0;
259 		timer->common.magic = 0;
260 		isc_task_detach(&timer->task);
261 		free(timer);
262 		return (result);
263 	}
264 
265 	*timerp = (isc_timer_t *)timer;
266 
267 	return (ISC_R_SUCCESS);
268 }
269 
270 isc_result_t
271 isc__timer_reset(isc_timer_t *timer0, const struct timespec *interval,
272 		 isc_boolean_t purge)
273 {
274 	isc__timer_t *timer = (isc__timer_t *)timer0;
275 	struct timespec now;
276 	isc__timermgr_t *manager;
277 	isc_result_t result;
278 
279 	/*
280 	 * Change the timer's type, expires, and interval values to the given
281 	 * values.  If 'purge' is ISC_TRUE, any pending events from this timer
282 	 * are purged from its task's event queue.
283 	 */
284 
285 	REQUIRE(VALID_TIMER(timer));
286 	manager = timer->manager;
287 	REQUIRE(VALID_MANAGER(manager));
288 	REQUIRE(interval != NULL);
289 	REQUIRE(timespecisset(interval));
290 
291 	/*
292 	 * Get current time.
293 	 */
294 	clock_gettime(CLOCK_REALTIME, &now);
295 
296 	if (purge)
297 		(void)isc_task_purgerange(timer->task,
298 					  timer,
299 					  ISC_TIMEREVENT_FIRSTEVENT,
300 					  ISC_TIMEREVENT_LASTEVENT,
301 					  NULL);
302 	timer->interval = *interval;
303 	if (timespecisset(interval)) {
304 		timespecadd(&now, interval, &timer->idle);
305 	} else {
306 		timespecclear(&timer->idle);
307 	}
308 
309 	result = schedule(timer, &now, ISC_TRUE);
310 
311 	return (result);
312 }
313 
314 void
315 isc__timer_touch(isc_timer_t *timer0) {
316 	isc__timer_t *timer = (isc__timer_t *)timer0;
317 	struct timespec now;
318 
319 	/*
320 	 * Set the last-touched time of 'timer' to the current time.
321 	 */
322 
323 	REQUIRE(VALID_TIMER(timer));
324 
325 	clock_gettime(CLOCK_REALTIME, &now);
326 	timespecadd(&now, &timer->interval, &timer->idle);
327 }
328 
329 void
330 isc__timer_attach(isc_timer_t *timer0, isc_timer_t **timerp) {
331 	isc__timer_t *timer = (isc__timer_t *)timer0;
332 
333 	/*
334 	 * Attach *timerp to timer.
335 	 */
336 
337 	REQUIRE(VALID_TIMER(timer));
338 	REQUIRE(timerp != NULL && *timerp == NULL);
339 
340 	timer->references++;
341 
342 	*timerp = (isc_timer_t *)timer;
343 }
344 
345 void
346 isc__timer_detach(isc_timer_t **timerp) {
347 	isc__timer_t *timer;
348 	isc_boolean_t free_timer = ISC_FALSE;
349 
350 	/*
351 	 * Detach *timerp from its timer.
352 	 */
353 
354 	REQUIRE(timerp != NULL);
355 	timer = (isc__timer_t *)*timerp;
356 	REQUIRE(VALID_TIMER(timer));
357 
358 	REQUIRE(timer->references > 0);
359 	timer->references--;
360 	if (timer->references == 0)
361 		free_timer = ISC_TRUE;
362 
363 	if (free_timer)
364 		destroy(timer);
365 
366 	*timerp = NULL;
367 }
368 
369 static void
370 dispatch(isc__timermgr_t *manager, struct timespec *now) {
371 	isc_boolean_t done = ISC_FALSE, post_event, need_schedule;
372 	isc_timerevent_t *event;
373 	isc_eventtype_t type = 0;
374 	isc__timer_t *timer;
375 	isc_result_t result;
376 	isc_boolean_t idle;
377 
378 	/*!
379 	 * The caller must be holding the manager lock.
380 	 */
381 
382 	while (manager->nscheduled > 0 && !done) {
383 		timer = isc_heap_element(manager->heap, 1);
384 		INSIST(timer != NULL);
385 		if (timespeccmp(now, &timer->due, >=)) {
386 			idle = ISC_FALSE;
387 
388 			if (timespecisset(&timer->idle) && timespeccmp(now,
389 			    &timer->idle, >=)) {
390 				idle = ISC_TRUE;
391 			}
392 			if (idle) {
393 				type = ISC_TIMEREVENT_IDLE;
394 				post_event = ISC_TRUE;
395 				need_schedule = ISC_FALSE;
396 			} else {
397 				/*
398 				 * Idle timer has been touched;
399 				 * reschedule.
400 				 */
401 				post_event = ISC_FALSE;
402 				need_schedule = ISC_TRUE;
403 			}
404 
405 			if (post_event) {
406 				/*
407 				 * XXX We could preallocate this event.
408 				 */
409 				event = (isc_timerevent_t *)isc_event_allocate(
410 							   timer,
411 							   type,
412 							   timer->action,
413 							   timer->arg,
414 							   sizeof(*event));
415 
416 				if (event != NULL) {
417 					event->due = timer->due;
418 					isc_task_send(timer->task,
419 						      ISC_EVENT_PTR(&event));
420 				} else
421 					UNEXPECTED_ERROR(__FILE__, __LINE__, "%s",
422 						 "couldn't allocate event");
423 			}
424 
425 			timer->index = 0;
426 			isc_heap_delete(manager->heap, 1);
427 			manager->nscheduled--;
428 
429 			if (need_schedule) {
430 				result = schedule(timer, now, ISC_FALSE);
431 				if (result != ISC_R_SUCCESS)
432 					UNEXPECTED_ERROR(__FILE__, __LINE__,
433 						"%s: %u",
434 						"couldn't schedule timer",
435 						result);
436 			}
437 		} else {
438 			manager->due = timer->due;
439 			done = ISC_TRUE;
440 		}
441 	}
442 }
443 
444 static isc_boolean_t
445 sooner(void *v1, void *v2) {
446 	isc__timer_t *t1, *t2;
447 
448 	t1 = v1;
449 	t2 = v2;
450 	REQUIRE(VALID_TIMER(t1));
451 	REQUIRE(VALID_TIMER(t2));
452 
453 	if (timespeccmp(&t1->due, &t2->due, <))
454 		return (ISC_TRUE);
455 	return (ISC_FALSE);
456 }
457 
458 static void
459 set_index(void *what, unsigned int index) {
460 	isc__timer_t *timer;
461 
462 	timer = what;
463 	REQUIRE(VALID_TIMER(timer));
464 
465 	timer->index = index;
466 }
467 
468 isc_result_t
469 isc__timermgr_create(isc_timermgr_t **managerp) {
470 	isc__timermgr_t *manager;
471 	isc_result_t result;
472 
473 	/*
474 	 * Create a timer manager.
475 	 */
476 
477 	REQUIRE(managerp != NULL && *managerp == NULL);
478 
479 	if (timermgr != NULL) {
480 		timermgr->refs++;
481 		*managerp = (isc_timermgr_t *)timermgr;
482 		return (ISC_R_SUCCESS);
483 	}
484 
485 	manager = malloc(sizeof(*manager));
486 	if (manager == NULL)
487 		return (ISC_R_NOMEMORY);
488 
489 	manager->common.impmagic = TIMER_MANAGER_MAGIC;
490 	manager->common.magic = ISCAPI_TIMERMGR_MAGIC;
491 	manager->done = ISC_FALSE;
492 	INIT_LIST(manager->timers);
493 	manager->nscheduled = 0;
494 	timespecclear(&manager->due);
495 	manager->heap = NULL;
496 	result = isc_heap_create(sooner, set_index, 0, &manager->heap);
497 	if (result != ISC_R_SUCCESS) {
498 		INSIST(result == ISC_R_NOMEMORY);
499 		free(manager);
500 		return (ISC_R_NOMEMORY);
501 	}
502 	manager->refs = 1;
503 	timermgr = manager;
504 
505 	*managerp = (isc_timermgr_t *)manager;
506 
507 	return (ISC_R_SUCCESS);
508 }
509 
510 void
511 isc__timermgr_destroy(isc_timermgr_t **managerp) {
512 	isc__timermgr_t *manager;
513 
514 	/*
515 	 * Destroy a timer manager.
516 	 */
517 
518 	REQUIRE(managerp != NULL);
519 	manager = (isc__timermgr_t *)*managerp;
520 	REQUIRE(VALID_MANAGER(manager));
521 
522 	manager->refs--;
523 	if (manager->refs > 0) {
524 		*managerp = NULL;
525 		return;
526 	}
527 	timermgr = NULL;
528 
529 	isc__timermgr_dispatch((isc_timermgr_t *)manager);
530 
531 	REQUIRE(EMPTY(manager->timers));
532 	manager->done = ISC_TRUE;
533 
534 	/*
535 	 * Clean up.
536 	 */
537 	isc_heap_destroy(&manager->heap);
538 	manager->common.impmagic = 0;
539 	manager->common.magic = 0;
540 	free(manager);
541 
542 	*managerp = NULL;
543 
544 	timermgr = NULL;
545 }
546 
547 isc_result_t
548 isc__timermgr_nextevent(isc_timermgr_t *manager0, struct timespec *when) {
549 	isc__timermgr_t *manager = (isc__timermgr_t *)manager0;
550 
551 	if (manager == NULL)
552 		manager = timermgr;
553 	if (manager == NULL || manager->nscheduled == 0)
554 		return (ISC_R_NOTFOUND);
555 	*when = manager->due;
556 	return (ISC_R_SUCCESS);
557 }
558 
559 void
560 isc__timermgr_dispatch(isc_timermgr_t *manager0) {
561 	isc__timermgr_t *manager = (isc__timermgr_t *)manager0;
562 	struct timespec now;
563 
564 	if (manager == NULL)
565 		manager = timermgr;
566 	if (manager == NULL)
567 		return;
568 	clock_gettime(CLOCK_REALTIME, &now);
569 	dispatch(manager, &now);
570 }
571 
572 isc_result_t
573 isc_timermgr_create(isc_timermgr_t **managerp) {
574 	return (isc__timermgr_create(managerp));
575 }
576 
577 void
578 isc_timermgr_destroy(isc_timermgr_t **managerp) {
579 	REQUIRE(*managerp != NULL && ISCAPI_TIMERMGR_VALID(*managerp));
580 
581 	isc__timermgr_destroy(managerp);
582 
583 	ENSURE(*managerp == NULL);
584 }
585 
586 isc_result_t
587 isc_timer_create(isc_timermgr_t *manager, const struct timespec *interval,
588 		 isc_task_t *task, isc_taskaction_t action, void *arg,
589 		 isc_timer_t **timerp)
590 {
591 	REQUIRE(ISCAPI_TIMERMGR_VALID(manager));
592 
593 	return (isc__timer_create(manager, interval,
594 				  task, action, arg, timerp));
595 }
596 
597 void
598 isc_timer_detach(isc_timer_t **timerp) {
599 	REQUIRE(timerp != NULL && ISCAPI_TIMER_VALID(*timerp));
600 
601 	isc__timer_detach(timerp);
602 
603 	ENSURE(*timerp == NULL);
604 }
605 
606 isc_result_t
607 isc_timer_reset(isc_timer_t *timer, const struct timespec *interval,
608 		isc_boolean_t purge)
609 {
610 	REQUIRE(ISCAPI_TIMER_VALID(timer));
611 
612 	return (isc__timer_reset(timer, interval, purge));
613 }
614 
615 void
616 isc_timer_touch(isc_timer_t *timer) {
617 	REQUIRE(ISCAPI_TIMER_VALID(timer));
618 
619 	isc__timer_touch(timer);
620 }
621