xref: /netbsd-src/external/mpl/bind/dist/lib/isc/timer.c (revision 8e33eff89e26cf71871ead62f0d5063e1313c33a)
1 /*	$NetBSD: timer.c,v 1.14 2024/09/22 00:14:08 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 /*! \file */
17 
18 #include <stdbool.h>
19 
20 #include <isc/app.h>
21 #include <isc/condition.h>
22 #include <isc/heap.h>
23 #include <isc/log.h>
24 #include <isc/magic.h>
25 #include <isc/mem.h>
26 #include <isc/once.h>
27 #include <isc/print.h>
28 #include <isc/refcount.h>
29 #include <isc/result.h>
30 #include <isc/task.h>
31 #include <isc/thread.h>
32 #include <isc/time.h>
33 #include <isc/timer.h>
34 #include <isc/util.h>
35 
36 #include "timer_p.h"
37 
38 #ifdef ISC_TIMER_TRACE
39 #define XTRACE(s)      fprintf(stderr, "%s\n", (s))
40 #define XTRACEID(s, t) fprintf(stderr, "%s %p\n", (s), (t))
41 #define XTRACETIME(s, d) \
42 	fprintf(stderr, "%s %u.%09u\n", (s), (d).seconds, (d).nanoseconds)
43 #define XTRACETIME2(s, d, n)                                      \
44 	fprintf(stderr, "%s %u.%09u %u.%09u\n", (s), (d).seconds, \
45 		(d).nanoseconds, (n).seconds, (n).nanoseconds)
46 #define XTRACETIMER(s, t, d)                                      \
47 	fprintf(stderr, "%s %p %u.%09u\n", (s), (t), (d).seconds, \
48 		(d).nanoseconds)
49 #else /* ifdef ISC_TIMER_TRACE */
50 #define XTRACE(s)
51 #define XTRACEID(s, t)
52 #define XTRACETIME(s, d)
53 #define XTRACETIME2(s, d, n)
54 #define XTRACETIMER(s, t, d)
55 #endif /* ISC_TIMER_TRACE */
56 
57 #define TIMER_MAGIC    ISC_MAGIC('T', 'I', 'M', 'R')
58 #define VALID_TIMER(t) ISC_MAGIC_VALID(t, TIMER_MAGIC)
59 
60 struct isc_timer {
61 	/*! Not locked. */
62 	unsigned int magic;
63 	isc_timermgr_t *manager;
64 	isc_mutex_t lock;
65 	/*! Locked by timer lock. */
66 	isc_time_t idle;
67 	ISC_LIST(isc_timerevent_t) active;
68 	/*! Locked by manager lock. */
69 	isc_timertype_t type;
70 	isc_time_t expires;
71 	isc_interval_t interval;
72 	isc_task_t *task;
73 	isc_taskaction_t action;
74 	void *arg;
75 	unsigned int index;
76 	isc_time_t due;
77 	LINK(isc_timer_t) link;
78 };
79 
80 #define TIMER_MANAGER_MAGIC ISC_MAGIC('T', 'I', 'M', 'M')
81 #define VALID_MANAGER(m)    ISC_MAGIC_VALID(m, TIMER_MANAGER_MAGIC)
82 
83 struct isc_timermgr {
84 	/* Not locked. */
85 	unsigned int magic;
86 	isc_mem_t *mctx;
87 	isc_mutex_t lock;
88 	/* Locked by manager lock. */
89 	bool done;
90 	LIST(isc_timer_t) timers;
91 	unsigned int nscheduled;
92 	isc_time_t due;
93 	isc_condition_t wakeup;
94 	isc_thread_t thread;
95 	isc_heap_t *heap;
96 };
97 
98 void
99 isc_timermgr_poke(isc_timermgr_t *manager);
100 
101 static inline isc_result_t
102 schedule(isc_timer_t *timer, isc_time_t *now, bool signal_ok) {
103 	isc_timermgr_t *manager;
104 	isc_time_t due;
105 
106 	/*!
107 	 * Note: the caller must ensure locking.
108 	 */
109 
110 	REQUIRE(timer->type != isc_timertype_inactive);
111 
112 	manager = timer->manager;
113 
114 	/*
115 	 * Compute the new due time.
116 	 */
117 	if (timer->type != isc_timertype_once) {
118 		isc_result_t result = isc_time_add(now, &timer->interval, &due);
119 		if (result != ISC_R_SUCCESS) {
120 			return (result);
121 		}
122 		if (timer->type == isc_timertype_limited &&
123 		    isc_time_compare(&timer->expires, &due) < 0)
124 		{
125 			due = timer->expires;
126 		}
127 	} else {
128 		if (isc_time_isepoch(&timer->idle)) {
129 			due = timer->expires;
130 		} else if (isc_time_isepoch(&timer->expires)) {
131 			due = timer->idle;
132 		} else if (isc_time_compare(&timer->idle, &timer->expires) < 0)
133 		{
134 			due = timer->idle;
135 		} else {
136 			due = timer->expires;
137 		}
138 	}
139 
140 	/*
141 	 * Schedule the timer.
142 	 */
143 
144 	if (timer->index > 0) {
145 		/*
146 		 * Already scheduled.
147 		 */
148 		int cmp = isc_time_compare(&due, &timer->due);
149 		timer->due = due;
150 		switch (cmp) {
151 		case -1:
152 			isc_heap_increased(manager->heap, timer->index);
153 			break;
154 		case 1:
155 			isc_heap_decreased(manager->heap, timer->index);
156 			break;
157 		case 0:
158 			/* Nothing to do. */
159 			break;
160 		}
161 	} else {
162 		timer->due = due;
163 		isc_heap_insert(manager->heap, timer);
164 		manager->nscheduled++;
165 	}
166 
167 	XTRACETIMER("schedule", timer, due);
168 
169 	/*
170 	 * If this timer is at the head of the queue, we need to ensure
171 	 * that we won't miss it if it has a more recent due time than
172 	 * the current "next" timer.  We do this either by waking up the
173 	 * run thread, or explicitly setting the value in the manager.
174 	 */
175 
176 	if (timer->index == 1 && signal_ok) {
177 		XTRACE("signal (schedule)");
178 		SIGNAL(&manager->wakeup);
179 	}
180 
181 	return (ISC_R_SUCCESS);
182 }
183 
184 static inline void
185 deschedule(isc_timer_t *timer) {
186 	isc_timermgr_t *manager;
187 
188 	/*
189 	 * The caller must ensure locking.
190 	 */
191 
192 	manager = timer->manager;
193 	if (timer->index > 0) {
194 		bool need_wakeup = false;
195 		if (timer->index == 1) {
196 			need_wakeup = true;
197 		}
198 		isc_heap_delete(manager->heap, timer->index);
199 		timer->index = 0;
200 		INSIST(manager->nscheduled > 0);
201 		manager->nscheduled--;
202 		if (need_wakeup) {
203 			XTRACE("signal (deschedule)");
204 			SIGNAL(&manager->wakeup);
205 		}
206 	}
207 }
208 
209 static void
210 timerevent_unlink(isc_timer_t *timer, isc_timerevent_t *event) {
211 	REQUIRE(ISC_LINK_LINKED(event, ev_timerlink));
212 	ISC_LIST_UNLINK(timer->active, event, ev_timerlink);
213 }
214 
215 static void
216 timerevent_destroy(isc_event_t *event0) {
217 	isc_timer_t *timer = event0->ev_destroy_arg;
218 	isc_timerevent_t *event = (isc_timerevent_t *)event0;
219 
220 	LOCK(&timer->lock);
221 	if (ISC_LINK_LINKED(event, ev_timerlink)) {
222 		/* The event was unlinked via timer_purge() */
223 		timerevent_unlink(timer, event);
224 	}
225 	UNLOCK(&timer->lock);
226 
227 	isc_mem_put(timer->manager->mctx, event, event0->ev_size);
228 }
229 
230 static void
231 timer_purge(isc_timer_t *timer) {
232 	isc_timerevent_t *event = NULL;
233 
234 	while ((event = ISC_LIST_HEAD(timer->active)) != NULL) {
235 		timerevent_unlink(timer, event);
236 		bool purged = isc_task_purgeevent(timer->task,
237 						  (isc_event_t *)event);
238 		UNLOCK(&timer->lock);
239 #if defined(UNIT_TESTING)
240 		usleep(100);
241 #endif
242 		if (purged) {
243 			isc_event_free((isc_event_t **)&event);
244 		} else {
245 			/*
246 			 * The event was processed while we were trying to
247 			 * purge it. The event's action is responsible for
248 			 * calling isc_event_free(), which in turn will call
249 			 * event->ev_destroy() (timerevent_destroy() here),
250 			 * which will unlink and destroy it.
251 			 */
252 		}
253 		LOCK(&timer->lock);
254 	}
255 }
256 
257 isc_result_t
258 isc_timer_create(isc_timermgr_t *manager, isc_timertype_t type,
259 		 const isc_time_t *expires, const isc_interval_t *interval,
260 		 isc_task_t *task, isc_taskaction_t action, void *arg,
261 		 isc_timer_t **timerp) {
262 	REQUIRE(VALID_MANAGER(manager));
263 	REQUIRE(task != NULL);
264 	REQUIRE(action != NULL);
265 
266 	isc_timer_t *timer;
267 	isc_result_t result;
268 	isc_time_t now;
269 
270 	/*
271 	 * Create a new 'type' timer managed by 'manager'.  The timers
272 	 * parameters are specified by 'expires' and 'interval'.  Events
273 	 * will be posted to 'task' and when dispatched 'action' will be
274 	 * called with 'arg' as the arg value.  The new timer is returned
275 	 * in 'timerp'.
276 	 */
277 	if (expires == NULL) {
278 		expires = isc_time_epoch;
279 	}
280 	if (interval == NULL) {
281 		interval = isc_interval_zero;
282 	}
283 	REQUIRE(type == isc_timertype_inactive ||
284 		!(isc_time_isepoch(expires) && isc_interval_iszero(interval)));
285 	REQUIRE(timerp != NULL && *timerp == NULL);
286 	REQUIRE(type != isc_timertype_limited ||
287 		!(isc_time_isepoch(expires) || isc_interval_iszero(interval)));
288 
289 	/*
290 	 * Get current time.
291 	 */
292 	if (type != isc_timertype_inactive) {
293 		TIME_NOW(&now);
294 	} else {
295 		/*
296 		 * We don't have to do this, but it keeps the compiler from
297 		 * complaining about "now" possibly being used without being
298 		 * set, even though it will never actually happen.
299 		 */
300 		isc_time_settoepoch(&now);
301 	}
302 
303 	timer = isc_mem_get(manager->mctx, sizeof(*timer));
304 
305 	timer->manager = manager;
306 
307 	if (type == isc_timertype_once && !isc_interval_iszero(interval)) {
308 		result = isc_time_add(&now, interval, &timer->idle);
309 		if (result != ISC_R_SUCCESS) {
310 			isc_mem_put(manager->mctx, timer, sizeof(*timer));
311 			return (result);
312 		}
313 	} else {
314 		isc_time_settoepoch(&timer->idle);
315 	}
316 
317 	timer->type = type;
318 	timer->expires = *expires;
319 	timer->interval = *interval;
320 	timer->task = NULL;
321 	isc_task_attach(task, &timer->task);
322 	timer->action = action;
323 	/*
324 	 * Removing the const attribute from "arg" is the best of two
325 	 * evils here.  If the timer->arg member is made const, then
326 	 * it affects a great many recipients of the timer event
327 	 * which did not pass in an "arg" that was truly const.
328 	 * Changing isc_timer_create() to not have "arg" prototyped as const,
329 	 * though, can cause compilers warnings for calls that *do*
330 	 * have a truly const arg.  The caller will have to carefully
331 	 * keep track of whether arg started as a true const.
332 	 */
333 	DE_CONST(arg, timer->arg);
334 	timer->index = 0;
335 	isc_mutex_init(&timer->lock);
336 	ISC_LINK_INIT(timer, link);
337 
338 	ISC_LIST_INIT(timer->active);
339 
340 	timer->magic = TIMER_MAGIC;
341 
342 	LOCK(&manager->lock);
343 
344 	/*
345 	 * Note we don't have to lock the timer like we normally would because
346 	 * there are no external references to it yet.
347 	 */
348 
349 	if (type != isc_timertype_inactive) {
350 		result = schedule(timer, &now, true);
351 	} else {
352 		result = ISC_R_SUCCESS;
353 	}
354 	if (result == ISC_R_SUCCESS) {
355 		*timerp = timer;
356 		APPEND(manager->timers, timer, link);
357 	}
358 
359 	UNLOCK(&manager->lock);
360 
361 	if (result != ISC_R_SUCCESS) {
362 		timer->magic = 0;
363 		isc_mutex_destroy(&timer->lock);
364 		isc_task_detach(&timer->task);
365 		isc_mem_put(manager->mctx, timer, sizeof(*timer));
366 		return (result);
367 	}
368 
369 	return (ISC_R_SUCCESS);
370 }
371 
372 isc_result_t
373 isc_timer_reset(isc_timer_t *timer, isc_timertype_t type,
374 		const isc_time_t *expires, const isc_interval_t *interval,
375 		bool purge) {
376 	isc_time_t now;
377 	isc_timermgr_t *manager;
378 	isc_result_t result;
379 
380 	/*
381 	 * Change the timer's type, expires, and interval values to the given
382 	 * values.  If 'purge' is true, any pending events from this timer
383 	 * are purged from its task's event queue.
384 	 */
385 
386 	REQUIRE(VALID_TIMER(timer));
387 	manager = timer->manager;
388 	REQUIRE(VALID_MANAGER(manager));
389 
390 	if (expires == NULL) {
391 		expires = isc_time_epoch;
392 	}
393 	if (interval == NULL) {
394 		interval = isc_interval_zero;
395 	}
396 	REQUIRE(type == isc_timertype_inactive ||
397 		!(isc_time_isepoch(expires) && isc_interval_iszero(interval)));
398 	REQUIRE(type != isc_timertype_limited ||
399 		!(isc_time_isepoch(expires) || isc_interval_iszero(interval)));
400 
401 	/*
402 	 * Get current time.
403 	 */
404 	if (type != isc_timertype_inactive) {
405 		TIME_NOW(&now);
406 	} else {
407 		/*
408 		 * We don't have to do this, but it keeps the compiler from
409 		 * complaining about "now" possibly being used without being
410 		 * set, even though it will never actually happen.
411 		 */
412 		isc_time_settoepoch(&now);
413 	}
414 
415 	LOCK(&manager->lock);
416 	LOCK(&timer->lock);
417 
418 	if (purge) {
419 		timer_purge(timer);
420 	}
421 	timer->type = type;
422 	timer->expires = *expires;
423 	timer->interval = *interval;
424 	if (type == isc_timertype_once && !isc_interval_iszero(interval)) {
425 		result = isc_time_add(&now, interval, &timer->idle);
426 	} else {
427 		isc_time_settoepoch(&timer->idle);
428 		result = ISC_R_SUCCESS;
429 	}
430 
431 	if (result == ISC_R_SUCCESS) {
432 		if (type == isc_timertype_inactive) {
433 			deschedule(timer);
434 			result = ISC_R_SUCCESS;
435 		} else {
436 			result = schedule(timer, &now, true);
437 		}
438 	}
439 
440 	UNLOCK(&timer->lock);
441 	UNLOCK(&manager->lock);
442 
443 	return (result);
444 }
445 
446 isc_timertype_t
447 isc_timer_gettype(isc_timer_t *timer) {
448 	isc_timertype_t t;
449 
450 	REQUIRE(VALID_TIMER(timer));
451 
452 	LOCK(&timer->lock);
453 	t = timer->type;
454 	UNLOCK(&timer->lock);
455 
456 	return (t);
457 }
458 
459 isc_result_t
460 isc_timer_touch(isc_timer_t *timer) {
461 	isc_result_t result;
462 	isc_time_t now;
463 
464 	/*
465 	 * Set the last-touched time of 'timer' to the current time.
466 	 */
467 
468 	REQUIRE(VALID_TIMER(timer));
469 
470 	LOCK(&timer->lock);
471 
472 	/*
473 	 * We'd like to
474 	 *
475 	 *	REQUIRE(timer->type == isc_timertype_once);
476 	 *
477 	 * but we cannot without locking the manager lock too, which we
478 	 * don't want to do.
479 	 */
480 
481 	TIME_NOW(&now);
482 	result = isc_time_add(&now, &timer->interval, &timer->idle);
483 
484 	UNLOCK(&timer->lock);
485 
486 	return (result);
487 }
488 
489 void
490 isc_timer_purge(isc_timer_t *timer) {
491 	REQUIRE(VALID_TIMER(timer));
492 
493 	LOCK(&timer->lock);
494 	timer_purge(timer);
495 	UNLOCK(&timer->lock);
496 }
497 
498 void
499 isc_timer_destroy(isc_timer_t **timerp) {
500 	isc_timer_t *timer = NULL;
501 	isc_timermgr_t *manager = NULL;
502 
503 	REQUIRE(timerp != NULL && VALID_TIMER(*timerp));
504 
505 	timer = *timerp;
506 	*timerp = NULL;
507 
508 	manager = timer->manager;
509 
510 	LOCK(&manager->lock);
511 
512 	LOCK(&timer->lock);
513 	timer_purge(timer);
514 	deschedule(timer);
515 	UNLOCK(&timer->lock);
516 
517 	UNLINK(manager->timers, timer, link);
518 
519 	UNLOCK(&manager->lock);
520 
521 	isc_task_detach(&timer->task);
522 	isc_mutex_destroy(&timer->lock);
523 	timer->magic = 0;
524 	isc_mem_put(manager->mctx, timer, sizeof(*timer));
525 }
526 
527 static void
528 timer_post_event(isc_timermgr_t *manager, isc_timer_t *timer,
529 		 isc_eventtype_t type) {
530 	isc_timerevent_t *event;
531 	XTRACEID("posting", timer);
532 
533 	event = (isc_timerevent_t *)isc_event_allocate(
534 		manager->mctx, timer, type, timer->action, timer->arg,
535 		sizeof(*event));
536 
537 	ISC_LINK_INIT(event, ev_timerlink);
538 	((isc_event_t *)event)->ev_destroy = timerevent_destroy;
539 	((isc_event_t *)event)->ev_destroy_arg = timer;
540 
541 	event->due = timer->due;
542 
543 	LOCK(&timer->lock);
544 	ISC_LIST_APPEND(timer->active, event, ev_timerlink);
545 	UNLOCK(&timer->lock);
546 
547 	isc_task_send(timer->task, ISC_EVENT_PTR(&event));
548 }
549 
550 static void
551 dispatch(isc_timermgr_t *manager, isc_time_t *now) {
552 	bool done = false, post_event, need_schedule;
553 	isc_eventtype_t type = 0;
554 	isc_timer_t *timer;
555 	isc_result_t result;
556 	bool idle;
557 
558 	/*!
559 	 * The caller must be holding the manager lock.
560 	 */
561 
562 	while (manager->nscheduled > 0 && !done) {
563 		timer = isc_heap_element(manager->heap, 1);
564 		INSIST(timer != NULL && timer->type != isc_timertype_inactive);
565 		if (isc_time_compare(now, &timer->due) >= 0) {
566 			if (timer->type == isc_timertype_ticker) {
567 				type = ISC_TIMEREVENT_TICK;
568 				post_event = true;
569 				need_schedule = true;
570 			} else if (timer->type == isc_timertype_limited) {
571 				int cmp;
572 				cmp = isc_time_compare(now, &timer->expires);
573 				if (cmp >= 0) {
574 					type = ISC_TIMEREVENT_LIFE;
575 					post_event = true;
576 					need_schedule = false;
577 				} else {
578 					type = ISC_TIMEREVENT_TICK;
579 					post_event = true;
580 					need_schedule = true;
581 				}
582 			} else if (!isc_time_isepoch(&timer->expires) &&
583 				   isc_time_compare(now, &timer->expires) >= 0)
584 			{
585 				type = ISC_TIMEREVENT_LIFE;
586 				post_event = true;
587 				need_schedule = false;
588 			} else {
589 				idle = false;
590 
591 				LOCK(&timer->lock);
592 				if (!isc_time_isepoch(&timer->idle) &&
593 				    isc_time_compare(now, &timer->idle) >= 0)
594 				{
595 					idle = true;
596 				}
597 				UNLOCK(&timer->lock);
598 				if (idle) {
599 					type = ISC_TIMEREVENT_IDLE;
600 					post_event = true;
601 					need_schedule = false;
602 				} else {
603 					/*
604 					 * Idle timer has been touched;
605 					 * reschedule.
606 					 */
607 					XTRACEID("idle reschedule", timer);
608 					post_event = false;
609 					need_schedule = true;
610 				}
611 			}
612 
613 			if (post_event) {
614 				timer_post_event(manager, timer, type);
615 			}
616 
617 			timer->index = 0;
618 			isc_heap_delete(manager->heap, 1);
619 			manager->nscheduled--;
620 
621 			if (need_schedule) {
622 				result = schedule(timer, now, false);
623 				if (result != ISC_R_SUCCESS) {
624 					UNEXPECTED_ERROR(
625 						"couldn't schedule timer: %s",
626 						isc_result_totext(result));
627 				}
628 			}
629 		} else {
630 			manager->due = timer->due;
631 			done = true;
632 		}
633 	}
634 }
635 
636 static isc_threadresult_t
637 run(void *uap) {
638 	isc_timermgr_t *manager = uap;
639 	isc_time_t now;
640 	isc_result_t result;
641 
642 	LOCK(&manager->lock);
643 	while (!manager->done) {
644 		TIME_NOW(&now);
645 
646 		XTRACETIME("running", now);
647 
648 		dispatch(manager, &now);
649 
650 		if (manager->nscheduled > 0) {
651 			XTRACETIME2("waituntil", manager->due, now);
652 			result = WAITUNTIL(&manager->wakeup, &manager->lock,
653 					   &manager->due);
654 			INSIST(result == ISC_R_SUCCESS ||
655 			       result == ISC_R_TIMEDOUT);
656 		} else {
657 			XTRACETIME("wait", now);
658 			WAIT(&manager->wakeup, &manager->lock);
659 		}
660 		XTRACE("wakeup");
661 	}
662 	UNLOCK(&manager->lock);
663 
664 	return ((isc_threadresult_t)0);
665 }
666 
667 static bool
668 sooner(void *v1, void *v2) {
669 	isc_timer_t *t1, *t2;
670 
671 	t1 = v1;
672 	t2 = v2;
673 	REQUIRE(VALID_TIMER(t1));
674 	REQUIRE(VALID_TIMER(t2));
675 
676 	if (isc_time_compare(&t1->due, &t2->due) < 0) {
677 		return (true);
678 	}
679 	return (false);
680 }
681 
682 static void
683 set_index(void *what, unsigned int index) {
684 	isc_timer_t *timer;
685 
686 	REQUIRE(VALID_TIMER(what));
687 	timer = what;
688 
689 	timer->index = index;
690 }
691 
692 isc_result_t
693 isc__timermgr_create(isc_mem_t *mctx, isc_timermgr_t **managerp) {
694 	isc_timermgr_t *manager;
695 
696 	/*
697 	 * Create a timer manager.
698 	 */
699 
700 	REQUIRE(managerp != NULL && *managerp == NULL);
701 
702 	manager = isc_mem_get(mctx, sizeof(*manager));
703 
704 	manager->magic = TIMER_MANAGER_MAGIC;
705 	manager->mctx = NULL;
706 	manager->done = false;
707 	INIT_LIST(manager->timers);
708 	manager->nscheduled = 0;
709 	isc_time_settoepoch(&manager->due);
710 	manager->heap = NULL;
711 	isc_heap_create(mctx, sooner, set_index, 0, &manager->heap);
712 	isc_mutex_init(&manager->lock);
713 	isc_mem_attach(mctx, &manager->mctx);
714 	isc_condition_init(&manager->wakeup);
715 	isc_thread_create(run, manager, &manager->thread);
716 	isc_thread_setname(manager->thread, "timer");
717 
718 	*managerp = manager;
719 
720 	return (ISC_R_SUCCESS);
721 }
722 
723 void
724 isc_timermgr_poke(isc_timermgr_t *manager) {
725 	REQUIRE(VALID_MANAGER(manager));
726 
727 	SIGNAL(&manager->wakeup);
728 }
729 
730 void
731 isc__timermgr_destroy(isc_timermgr_t **managerp) {
732 	isc_timermgr_t *manager;
733 
734 	/*
735 	 * Destroy a timer manager.
736 	 */
737 
738 	REQUIRE(managerp != NULL);
739 	manager = *managerp;
740 	REQUIRE(VALID_MANAGER(manager));
741 
742 	LOCK(&manager->lock);
743 
744 	REQUIRE(EMPTY(manager->timers));
745 	manager->done = true;
746 
747 	XTRACE("signal (destroy)");
748 	SIGNAL(&manager->wakeup);
749 
750 	UNLOCK(&manager->lock);
751 
752 	/*
753 	 * Wait for thread to exit.
754 	 */
755 	isc_thread_join(manager->thread, NULL);
756 
757 	/*
758 	 * Clean up.
759 	 */
760 	(void)isc_condition_destroy(&manager->wakeup);
761 	isc_mutex_destroy(&manager->lock);
762 	isc_heap_destroy(&manager->heap);
763 	manager->magic = 0;
764 	isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager));
765 
766 	*managerp = NULL;
767 }
768