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