xref: /openbsd/usr.bin/dig/lib/isc/timer.c (revision d415bd75)
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.25 2020/09/14 08:40:44 florian Exp $ */
18 
19 /*! \file */
20 
21 #include <sys/time.h>
22 #include <stdlib.h>
23 #include <time.h>
24 #include <isc/heap.h>
25 #include <isc/task.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 	int			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 		 int 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 1, 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 	clock_gettime(CLOCK_MONOTONIC, &now);
279 	timespecadd(&now, &timer->interval, &timer->idle);
280 }
281 
282 void
283 isc_timer_detach(isc_timer_t **timerp) {
284 	isc_timer_t *timer;
285 	int free_timer = 0;
286 
287 	/*
288 	 * Detach *timerp from its timer.
289 	 */
290 
291 	REQUIRE(timerp != NULL);
292 	timer = (isc_timer_t *)*timerp;
293 
294 	REQUIRE(timer->references > 0);
295 	timer->references--;
296 	if (timer->references == 0)
297 		free_timer = 1;
298 
299 	if (free_timer)
300 		destroy(timer);
301 
302 	*timerp = NULL;
303 }
304 
305 static void
306 dispatch(isc_timermgr_t *manager, struct timespec *now) {
307 	int done = 0, post_event, need_schedule;
308 	isc_timerevent_t *event;
309 	isc_eventtype_t type = 0;
310 	isc_timer_t *timer;
311 	isc_result_t result;
312 	int idle;
313 
314 	/*!
315 	 * The caller must be holding the manager lock.
316 	 */
317 
318 	while (manager->nscheduled > 0 && !done) {
319 		timer = isc_heap_element(manager->heap, 1);
320 		INSIST(timer != NULL);
321 		if (timespeccmp(now, &timer->due, >=)) {
322 			idle = 0;
323 
324 			if (timespecisset(&timer->idle) && timespeccmp(now,
325 			    &timer->idle, >=)) {
326 				idle = 1;
327 			}
328 			if (idle) {
329 				type = ISC_TIMEREVENT_IDLE;
330 				post_event = 1;
331 				need_schedule = 0;
332 			} else {
333 				/*
334 				 * Idle timer has been touched;
335 				 * reschedule.
336 				 */
337 				post_event = 0;
338 				need_schedule = 1;
339 			}
340 
341 			if (post_event) {
342 				/*
343 				 * XXX We could preallocate this event.
344 				 */
345 				event = (isc_timerevent_t *)isc_event_allocate(
346 							   timer,
347 							   type,
348 							   timer->action,
349 							   timer->arg,
350 							   sizeof(*event));
351 
352 				if (event != NULL) {
353 					event->due = timer->due;
354 					isc_task_send(timer->task,
355 						      ISC_EVENT_PTR(&event));
356 				} else
357 					UNEXPECTED_ERROR(__FILE__, __LINE__, "%s",
358 						 "couldn't allocate event");
359 			}
360 
361 			timer->index = 0;
362 			isc_heap_delete(manager->heap, 1);
363 			manager->nscheduled--;
364 
365 			if (need_schedule) {
366 				result = schedule(timer);
367 				if (result != ISC_R_SUCCESS)
368 					UNEXPECTED_ERROR(__FILE__, __LINE__,
369 						"%s: %u",
370 						"couldn't schedule timer",
371 						result);
372 			}
373 		} else {
374 			manager->due = timer->due;
375 			done = 1;
376 		}
377 	}
378 }
379 
380 static int
381 sooner(void *v1, void *v2) {
382 	isc_timer_t *t1, *t2;
383 
384 	t1 = v1;
385 	t2 = v2;
386 
387 	if (timespeccmp(&t1->due, &t2->due, <))
388 		return (1);
389 	return (0);
390 }
391 
392 static void
393 set_index(void *what, unsigned int index) {
394 	isc_timer_t *timer;
395 
396 	timer = what;
397 
398 	timer->index = index;
399 }
400 
401 isc_result_t
402 isc_timermgr_create(isc_timermgr_t **managerp) {
403 	isc_timermgr_t *manager;
404 	isc_result_t result;
405 
406 	/*
407 	 * Create a timer manager.
408 	 */
409 
410 	REQUIRE(managerp != NULL && *managerp == NULL);
411 
412 	if (timermgr != NULL) {
413 		timermgr->refs++;
414 		*managerp = (isc_timermgr_t *)timermgr;
415 		return (ISC_R_SUCCESS);
416 	}
417 
418 	manager = malloc(sizeof(*manager));
419 	if (manager == NULL)
420 		return (ISC_R_NOMEMORY);
421 
422 	manager->done = 0;
423 	INIT_LIST(manager->timers);
424 	manager->nscheduled = 0;
425 	timespecclear(&manager->due);
426 	manager->heap = NULL;
427 	result = isc_heap_create(sooner, set_index, 0, &manager->heap);
428 	if (result != ISC_R_SUCCESS) {
429 		INSIST(result == ISC_R_NOMEMORY);
430 		free(manager);
431 		return (ISC_R_NOMEMORY);
432 	}
433 	manager->refs = 1;
434 	timermgr = manager;
435 
436 	*managerp = (isc_timermgr_t *)manager;
437 
438 	return (ISC_R_SUCCESS);
439 }
440 
441 void
442 isc_timermgr_destroy(isc_timermgr_t **managerp) {
443 	isc_timermgr_t *manager;
444 
445 	/*
446 	 * Destroy a timer manager.
447 	 */
448 
449 	REQUIRE(managerp != NULL);
450 	manager = (isc_timermgr_t *)*managerp;
451 
452 	manager->refs--;
453 	if (manager->refs > 0) {
454 		*managerp = NULL;
455 		return;
456 	}
457 	timermgr = NULL;
458 
459 	isc_timermgr_dispatch((isc_timermgr_t *)manager);
460 
461 	REQUIRE(EMPTY(manager->timers));
462 	manager->done = 1;
463 
464 	/*
465 	 * Clean up.
466 	 */
467 	isc_heap_destroy(&manager->heap);
468 	free(manager);
469 
470 	*managerp = NULL;
471 
472 	timermgr = NULL;
473 }
474 
475 isc_result_t
476 isc_timermgr_nextevent(isc_timermgr_t *manager0, struct timespec *when) {
477 	isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
478 
479 	if (manager == NULL)
480 		manager = timermgr;
481 	if (manager == NULL || manager->nscheduled == 0)
482 		return (ISC_R_NOTFOUND);
483 	*when = manager->due;
484 	return (ISC_R_SUCCESS);
485 }
486 
487 void
488 isc_timermgr_dispatch(isc_timermgr_t *manager0) {
489 	isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
490 	struct timespec now;
491 
492 	if (manager == NULL)
493 		manager = timermgr;
494 	if (manager == NULL)
495 		return;
496 	clock_gettime(CLOCK_MONOTONIC, &now);
497 	dispatch(manager, &now);
498 }
499 
500