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