xref: /openbsd-src/usr.bin/dig/lib/isc/timer.c (revision ad5cf5382279f230a235589e749cc8d438dfa7ab)
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.21 2020/02/22 19:50:05 jung Exp $ */
18 
19 /*! \file */
20 
21 
22 #include <stdlib.h>
23 #include <isc/heap.h>
24 #include <isc/task.h>
25 #include <isc/time.h>
26 #include <isc/timer.h>
27 #include <isc/util.h>
28 
29 #include "timer_p.h"
30 
31 typedef struct isc_timer isc_timer_t;
32 typedef struct isc_timermgr isc_timermgr_t;
33 
34 struct isc_timer {
35 	/*! Not locked. */
36 	isc_timermgr_t *		manager;
37 	/*! Locked by timer lock. */
38 	unsigned int			references;
39 	struct timespec			idle;
40 	/*! Locked by manager lock. */
41 	struct timespec			interval;
42 	isc_task_t *			task;
43 	isc_taskaction_t		action;
44 	void *				arg;
45 	unsigned int			index;
46 	struct timespec			due;
47 	LINK(isc_timer_t)		link;
48 };
49 
50 struct isc_timermgr {
51 	/* Not locked. */
52 	/* Locked by manager lock. */
53 	isc_boolean_t			done;
54 	LIST(isc_timer_t)		timers;
55 	unsigned int			nscheduled;
56 	struct timespec			due;
57 	unsigned int			refs;
58 	isc_heap_t *			heap;
59 };
60 
61 /*%
62  * The following are intended for internal use (indicated by "isc__"
63  * prefix) but are not declared as static, allowing direct access from
64  * unit tests etc.
65  */
66 
67 /*!
68  * If the manager is supposed to be shared, there can be only one.
69  */
70 static isc_timermgr_t *timermgr = NULL;
71 
72 static inline isc_result_t
73 schedule(isc_timer_t *timer) {
74 	isc_result_t result;
75 	isc_timermgr_t *manager;
76 	struct timespec due;
77 
78 	/*!
79 	 * Note: the caller must ensure locking.
80 	 */
81 
82 	manager = timer->manager;
83 
84 	/*
85 	 * Compute the new due time.
86 	 */
87 	due = timer->idle;
88 
89 	/*
90 	 * Schedule the timer.
91 	 */
92 
93 	if (timer->index > 0) {
94 		/*
95 		 * Already scheduled.
96 		 */
97 		if (timespeccmp(&due, &timer->due, <))
98 		    isc_heap_increased(manager->heap, timer->index);
99 		else if (timespeccmp(&due, &timer->due, >))
100 		    isc_heap_decreased(manager->heap, timer->index);
101 
102 		timer->due = due;
103 	} else {
104 		timer->due = due;
105 		result = isc_heap_insert(manager->heap, timer);
106 		if (result != ISC_R_SUCCESS) {
107 			INSIST(result == ISC_R_NOMEMORY);
108 			return (ISC_R_NOMEMORY);
109 		}
110 		manager->nscheduled++;
111 	}
112 
113 	/*
114 	 * If this timer is at the head of the queue, we need to ensure
115 	 * that we won't miss it if it has a more recent due time than
116 	 * the current "next" timer.  We do this either by waking up the
117 	 * run thread, or explicitly setting the value in the manager.
118 	 */
119 	if (timer->index == 1 && timespeccmp(&timer->due, &manager->due, <))
120 		manager->due = timer->due;
121 
122 	return (ISC_R_SUCCESS);
123 }
124 
125 static inline void
126 deschedule(isc_timer_t *timer) {
127 	isc_timermgr_t *manager;
128 
129 	/*
130 	 * The caller must ensure locking.
131 	 */
132 
133 	manager = timer->manager;
134 	if (timer->index > 0) {
135 		isc_heap_delete(manager->heap, timer->index);
136 		timer->index = 0;
137 		INSIST(manager->nscheduled > 0);
138 		manager->nscheduled--;
139 	}
140 }
141 
142 static void
143 destroy(isc_timer_t *timer) {
144 	isc_timermgr_t *manager = timer->manager;
145 
146 	/*
147 	 * The caller must ensure it is safe to destroy the timer.
148 	 */
149 
150 	(void)isc_task_purgerange(timer->task,
151 				  timer,
152 				  ISC_TIMEREVENT_FIRSTEVENT,
153 				  ISC_TIMEREVENT_LASTEVENT,
154 				  NULL);
155 	deschedule(timer);
156 	UNLINK(manager->timers, timer, link);
157 
158 	isc_task_detach(&timer->task);
159 	free(timer);
160 }
161 
162 isc_result_t
163 isc_timer_create(isc_timermgr_t *manager0, const struct timespec *interval,
164 		  isc_task_t *task, isc_taskaction_t action, void *arg,
165 		  isc_timer_t **timerp)
166 {
167 	isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
168 	isc_timer_t *timer;
169 	isc_result_t result;
170 	struct timespec now;
171 
172 	/*
173 	 * Create a new 'type' timer managed by 'manager'.  The timers
174 	 * parameters are specified by 'interval'.  Events
175 	 * will be posted to 'task' and when dispatched 'action' will be
176 	 * called with 'arg' as the arg value.  The new timer is returned
177 	 * in 'timerp'.
178 	 */
179 
180 	REQUIRE(task != NULL);
181 	REQUIRE(action != NULL);
182 	REQUIRE(interval != NULL);
183 	REQUIRE(timespecisset(interval));
184 	REQUIRE(timerp != NULL && *timerp == NULL);
185 
186 	/*
187 	 * Get current time.
188 	 */
189 	clock_gettime(CLOCK_MONOTONIC, &now);
190 
191 	timer = malloc(sizeof(*timer));
192 	if (timer == NULL)
193 		return (ISC_R_NOMEMORY);
194 
195 	timer->manager = manager;
196 	timer->references = 1;
197 
198 	if (timespecisset(interval))
199 		timespecadd(&now, interval, &timer->idle);
200 
201 	timer->interval = *interval;
202 	timer->task = NULL;
203 	isc_task_attach(task, &timer->task);
204 	timer->action = action;
205 	/*
206 	 * Removing the const attribute from "arg" is the best of two
207 	 * evils here.  If the timer->arg member is made const, then
208 	 * it affects a great many recipients of the timer event
209 	 * which did not pass in an "arg" that was truly const.
210 	 * Changing isc_timer_create() to not have "arg" prototyped as const,
211 	 * though, can cause compilers warnings for calls that *do*
212 	 * have a truly const arg.  The caller will have to carefully
213 	 * keep track of whether arg started as a true const.
214 	 */
215 	DE_CONST(arg, timer->arg);
216 	timer->index = 0;
217 	ISC_LINK_INIT(timer, link);
218 
219 	result = schedule(timer);
220 	if (result == ISC_R_SUCCESS)
221 		APPEND(manager->timers, timer, link);
222 
223 	if (result != ISC_R_SUCCESS) {
224 		isc_task_detach(&timer->task);
225 		free(timer);
226 		return (result);
227 	}
228 
229 	*timerp = (isc_timer_t *)timer;
230 
231 	return (ISC_R_SUCCESS);
232 }
233 
234 isc_result_t
235 isc_timer_reset(isc_timer_t *timer, const struct timespec *interval,
236 		 isc_boolean_t purge)
237 {
238 	struct timespec now;
239 	isc_result_t result;
240 
241 	/*
242 	 * Change the timer's type, expires, and interval values to the given
243 	 * values.  If 'purge' is ISC_TRUE, any pending events from this timer
244 	 * are purged from its task's event queue.
245 	 */
246 
247 	REQUIRE(interval != NULL);
248 	REQUIRE(timespecisset(interval));
249 
250 	/*
251 	 * Get current time.
252 	 */
253 	clock_gettime(CLOCK_MONOTONIC, &now);
254 
255 	if (purge)
256 		(void)isc_task_purgerange(timer->task,
257 					  timer,
258 					  ISC_TIMEREVENT_FIRSTEVENT,
259 					  ISC_TIMEREVENT_LASTEVENT,
260 					  NULL);
261 	timer->interval = *interval;
262 	if (timespecisset(interval)) {
263 		timespecadd(&now, interval, &timer->idle);
264 	} else {
265 		timespecclear(&timer->idle);
266 	}
267 
268 	result = schedule(timer);
269 
270 	return (result);
271 }
272 
273 void
274 isc_timer_touch(isc_timer_t *timer) {
275 	struct timespec now;
276 
277 	/*
278 	 * Set the last-touched time of 'timer' to the current time.
279 	 */
280 
281 
282 	clock_gettime(CLOCK_MONOTONIC, &now);
283 	timespecadd(&now, &timer->interval, &timer->idle);
284 }
285 
286 void
287 isc_timer_detach(isc_timer_t **timerp) {
288 	isc_timer_t *timer;
289 	isc_boolean_t free_timer = ISC_FALSE;
290 
291 	/*
292 	 * Detach *timerp from its timer.
293 	 */
294 
295 	REQUIRE(timerp != NULL);
296 	timer = (isc_timer_t *)*timerp;
297 
298 	REQUIRE(timer->references > 0);
299 	timer->references--;
300 	if (timer->references == 0)
301 		free_timer = ISC_TRUE;
302 
303 	if (free_timer)
304 		destroy(timer);
305 
306 	*timerp = NULL;
307 }
308 
309 static void
310 dispatch(isc_timermgr_t *manager, struct timespec *now) {
311 	isc_boolean_t done = ISC_FALSE, post_event, need_schedule;
312 	isc_timerevent_t *event;
313 	isc_eventtype_t type = 0;
314 	isc_timer_t *timer;
315 	isc_result_t result;
316 	isc_boolean_t idle;
317 
318 	/*!
319 	 * The caller must be holding the manager lock.
320 	 */
321 
322 	while (manager->nscheduled > 0 && !done) {
323 		timer = isc_heap_element(manager->heap, 1);
324 		INSIST(timer != NULL);
325 		if (timespeccmp(now, &timer->due, >=)) {
326 			idle = ISC_FALSE;
327 
328 			if (timespecisset(&timer->idle) && timespeccmp(now,
329 			    &timer->idle, >=)) {
330 				idle = ISC_TRUE;
331 			}
332 			if (idle) {
333 				type = ISC_TIMEREVENT_IDLE;
334 				post_event = ISC_TRUE;
335 				need_schedule = ISC_FALSE;
336 			} else {
337 				/*
338 				 * Idle timer has been touched;
339 				 * reschedule.
340 				 */
341 				post_event = ISC_FALSE;
342 				need_schedule = ISC_TRUE;
343 			}
344 
345 			if (post_event) {
346 				/*
347 				 * XXX We could preallocate this event.
348 				 */
349 				event = (isc_timerevent_t *)isc_event_allocate(
350 							   timer,
351 							   type,
352 							   timer->action,
353 							   timer->arg,
354 							   sizeof(*event));
355 
356 				if (event != NULL) {
357 					event->due = timer->due;
358 					isc_task_send(timer->task,
359 						      ISC_EVENT_PTR(&event));
360 				} else
361 					UNEXPECTED_ERROR(__FILE__, __LINE__, "%s",
362 						 "couldn't allocate event");
363 			}
364 
365 			timer->index = 0;
366 			isc_heap_delete(manager->heap, 1);
367 			manager->nscheduled--;
368 
369 			if (need_schedule) {
370 				result = schedule(timer);
371 				if (result != ISC_R_SUCCESS)
372 					UNEXPECTED_ERROR(__FILE__, __LINE__,
373 						"%s: %u",
374 						"couldn't schedule timer",
375 						result);
376 			}
377 		} else {
378 			manager->due = timer->due;
379 			done = ISC_TRUE;
380 		}
381 	}
382 }
383 
384 static isc_boolean_t
385 sooner(void *v1, void *v2) {
386 	isc_timer_t *t1, *t2;
387 
388 	t1 = v1;
389 	t2 = v2;
390 
391 	if (timespeccmp(&t1->due, &t2->due, <))
392 		return (ISC_TRUE);
393 	return (ISC_FALSE);
394 }
395 
396 static void
397 set_index(void *what, unsigned int index) {
398 	isc_timer_t *timer;
399 
400 	timer = what;
401 
402 	timer->index = index;
403 }
404 
405 isc_result_t
406 isc_timermgr_create(isc_timermgr_t **managerp) {
407 	isc_timermgr_t *manager;
408 	isc_result_t result;
409 
410 	/*
411 	 * Create a timer manager.
412 	 */
413 
414 	REQUIRE(managerp != NULL && *managerp == NULL);
415 
416 	if (timermgr != NULL) {
417 		timermgr->refs++;
418 		*managerp = (isc_timermgr_t *)timermgr;
419 		return (ISC_R_SUCCESS);
420 	}
421 
422 	manager = malloc(sizeof(*manager));
423 	if (manager == NULL)
424 		return (ISC_R_NOMEMORY);
425 
426 	manager->done = ISC_FALSE;
427 	INIT_LIST(manager->timers);
428 	manager->nscheduled = 0;
429 	timespecclear(&manager->due);
430 	manager->heap = NULL;
431 	result = isc_heap_create(sooner, set_index, 0, &manager->heap);
432 	if (result != ISC_R_SUCCESS) {
433 		INSIST(result == ISC_R_NOMEMORY);
434 		free(manager);
435 		return (ISC_R_NOMEMORY);
436 	}
437 	manager->refs = 1;
438 	timermgr = manager;
439 
440 	*managerp = (isc_timermgr_t *)manager;
441 
442 	return (ISC_R_SUCCESS);
443 }
444 
445 void
446 isc_timermgr_destroy(isc_timermgr_t **managerp) {
447 	isc_timermgr_t *manager;
448 
449 	/*
450 	 * Destroy a timer manager.
451 	 */
452 
453 	REQUIRE(managerp != NULL);
454 	manager = (isc_timermgr_t *)*managerp;
455 
456 	manager->refs--;
457 	if (manager->refs > 0) {
458 		*managerp = NULL;
459 		return;
460 	}
461 	timermgr = NULL;
462 
463 	isc_timermgr_dispatch((isc_timermgr_t *)manager);
464 
465 	REQUIRE(EMPTY(manager->timers));
466 	manager->done = ISC_TRUE;
467 
468 	/*
469 	 * Clean up.
470 	 */
471 	isc_heap_destroy(&manager->heap);
472 	free(manager);
473 
474 	*managerp = NULL;
475 
476 	timermgr = NULL;
477 }
478 
479 isc_result_t
480 isc_timermgr_nextevent(isc_timermgr_t *manager0, struct timespec *when) {
481 	isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
482 
483 	if (manager == NULL)
484 		manager = timermgr;
485 	if (manager == NULL || manager->nscheduled == 0)
486 		return (ISC_R_NOTFOUND);
487 	*when = manager->due;
488 	return (ISC_R_SUCCESS);
489 }
490 
491 void
492 isc_timermgr_dispatch(isc_timermgr_t *manager0) {
493 	isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
494 	struct timespec now;
495 
496 	if (manager == NULL)
497 		manager = timermgr;
498 	if (manager == NULL)
499 		return;
500 	clock_gettime(CLOCK_MONOTONIC, &now);
501 	dispatch(manager, &now);
502 }
503 
504