xref: /netbsd-src/external/mpl/bind/dist/tests/isc/timer_test.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: timer_test.c,v 1.3 2025/01/26 16:25:50 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 #include <inttypes.h>
17 #include <sched.h> /* IWYU pragma: keep */
18 #include <setjmp.h>
19 #include <stdarg.h>
20 #include <stddef.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 
25 #define UNIT_TESTING
26 #include <cmocka.h>
27 
28 #include <isc/atomic.h>
29 #include <isc/commandline.h>
30 #include <isc/condition.h>
31 #include <isc/job.h>
32 #include <isc/loop.h>
33 #include <isc/mem.h>
34 #include <isc/os.h>
35 #include <isc/time.h>
36 #include <isc/timer.h>
37 #include <isc/util.h>
38 
39 #include "timer.c"
40 
41 #include <tests/isc.h>
42 
43 /* Set to true (or use -v option) for verbose output */
44 static bool verbose = true;
45 
46 #define FUDGE_SECONDS	  0	    /* in absence of clock_getres() */
47 #define FUDGE_NANOSECONDS 500000000 /* in absence of clock_getres() */
48 
49 static isc_timer_t *timer = NULL;
50 static isc_time_t endtime;
51 static isc_mutex_t lasttime_mx;
52 static isc_time_t lasttime;
53 static int seconds;
54 static int nanoseconds;
55 static atomic_int_fast32_t eventcnt;
56 static atomic_uint_fast32_t errcnt;
57 static int nevents;
58 
59 typedef struct setup_test_arg {
60 	isc_timertype_t timertype;
61 	isc_interval_t *interval;
62 	isc_job_cb action;
63 } setup_test_arg_t;
64 
65 static void
66 setup_test_run(void *data) {
67 	isc_timertype_t timertype = ((setup_test_arg_t *)data)->timertype;
68 	isc_interval_t *interval = ((setup_test_arg_t *)data)->interval;
69 	isc_job_cb action = ((setup_test_arg_t *)data)->action;
70 
71 	isc_mutex_lock(&lasttime_mx);
72 	lasttime = isc_time_now();
73 	UNLOCK(&lasttime_mx);
74 
75 	isc_timer_create(mainloop, action, (void *)timertype, &timer);
76 	isc_timer_start(timer, timertype, interval);
77 }
78 
79 static void
80 setup_test(isc_timertype_t timertype, isc_interval_t *interval,
81 	   isc_job_cb action) {
82 	setup_test_arg_t arg = { .timertype = timertype,
83 				 .interval = interval,
84 				 .action = action };
85 
86 	isc_time_settoepoch(&endtime);
87 	atomic_init(&eventcnt, 0);
88 
89 	isc_mutex_init(&lasttime_mx);
90 
91 	atomic_store(&errcnt, ISC_R_SUCCESS);
92 
93 	isc_loop_setup(mainloop, setup_test_run, &arg);
94 	isc_loopmgr_run(loopmgr);
95 
96 	assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
97 
98 	isc_mutex_destroy(&lasttime_mx);
99 }
100 
101 static void
102 set_global_error(isc_result_t result) {
103 	(void)atomic_compare_exchange_strong(
104 		&errcnt, &(uint_fast32_t){ ISC_R_SUCCESS }, result);
105 }
106 
107 static void
108 subthread_assert_true(bool expected, const char *file, unsigned int line) {
109 	if (!expected) {
110 		printf("# %s:%u subthread_assert_true\n", file, line);
111 		set_global_error(ISC_R_UNEXPECTED);
112 	}
113 }
114 #define subthread_assert_true(expected) \
115 	subthread_assert_true(expected, __FILE__, __LINE__)
116 
117 static void
118 subthread_assert_result_equal(isc_result_t result, isc_result_t expected,
119 			      const char *file, unsigned int line) {
120 	if (result != expected) {
121 		printf("# %s:%u subthread_assert_result_equal(%u != %u)\n",
122 		       file, line, (unsigned int)result,
123 		       (unsigned int)expected);
124 		set_global_error(result);
125 	}
126 }
127 #define subthread_assert_result_equal(observed, expected) \
128 	subthread_assert_result_equal(observed, expected, __FILE__, __LINE__)
129 
130 static void
131 ticktock(void *arg) {
132 	isc_result_t result;
133 	isc_time_t now;
134 	isc_time_t base;
135 	isc_time_t ulim;
136 	isc_time_t llim;
137 	isc_interval_t interval;
138 	int tick = atomic_fetch_add(&eventcnt, 1);
139 
140 	UNUSED(arg);
141 
142 	if (verbose) {
143 		print_message("# tick %d\n", tick);
144 	}
145 
146 	now = isc_time_now();
147 
148 	isc_interval_set(&interval, seconds, nanoseconds);
149 	isc_mutex_lock(&lasttime_mx);
150 	result = isc_time_add(&lasttime, &interval, &base);
151 	isc_mutex_unlock(&lasttime_mx);
152 	subthread_assert_result_equal(result, ISC_R_SUCCESS);
153 
154 	isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
155 	result = isc_time_add(&base, &interval, &ulim);
156 	subthread_assert_result_equal(result, ISC_R_SUCCESS);
157 
158 	result = isc_time_subtract(&base, &interval, &llim);
159 	subthread_assert_result_equal(result, ISC_R_SUCCESS);
160 
161 	subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
162 	subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
163 
164 	isc_interval_set(&interval, 0, 0);
165 	isc_mutex_lock(&lasttime_mx);
166 	result = isc_time_add(&now, &interval, &lasttime);
167 	isc_mutex_unlock(&lasttime_mx);
168 	subthread_assert_result_equal(result, ISC_R_SUCCESS);
169 
170 	if (atomic_load(&eventcnt) == nevents) {
171 		endtime = isc_time_now();
172 		isc_timer_destroy(&timer);
173 		isc_loopmgr_shutdown(loopmgr);
174 	}
175 }
176 
177 /*
178  * Individual unit tests
179  */
180 
181 /* timer type ticker */
182 ISC_RUN_TEST_IMPL(ticker) {
183 	isc_interval_t interval;
184 
185 	UNUSED(state);
186 
187 	nevents = 12;
188 	seconds = 0;
189 	nanoseconds = 500000000;
190 
191 	isc_interval_set(&interval, seconds, nanoseconds);
192 
193 	setup_test(isc_timertype_ticker, &interval, ticktock);
194 }
195 
196 static void
197 test_idle(void *arg) {
198 	isc_result_t result;
199 	isc_time_t now;
200 	isc_time_t base;
201 	isc_time_t ulim;
202 	isc_time_t llim;
203 	isc_interval_t interval;
204 	int tick = atomic_fetch_add(&eventcnt, 1);
205 
206 	UNUSED(arg);
207 
208 	if (verbose) {
209 		print_message("# tick %d\n", tick);
210 	}
211 
212 	now = isc_time_now();
213 
214 	isc_interval_set(&interval, seconds, nanoseconds);
215 	isc_mutex_lock(&lasttime_mx);
216 	result = isc_time_add(&lasttime, &interval, &base);
217 	isc_mutex_unlock(&lasttime_mx);
218 	subthread_assert_result_equal(result, ISC_R_SUCCESS);
219 
220 	isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
221 	result = isc_time_add(&base, &interval, &ulim);
222 	subthread_assert_result_equal(result, ISC_R_SUCCESS);
223 
224 	result = isc_time_subtract(&base, &interval, &llim);
225 	subthread_assert_result_equal(result, ISC_R_SUCCESS);
226 
227 	subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
228 	subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
229 
230 	isc_interval_set(&interval, 0, 0);
231 	isc_mutex_lock(&lasttime_mx);
232 	isc_time_add(&now, &interval, &lasttime);
233 	isc_mutex_unlock(&lasttime_mx);
234 
235 	isc_timer_destroy(&timer);
236 	isc_loopmgr_shutdown(loopmgr);
237 }
238 
239 /* timer type once idles out */
240 ISC_RUN_TEST_IMPL(once_idle) {
241 	isc_interval_t interval;
242 
243 	UNUSED(state);
244 
245 	nevents = 1;
246 	seconds = 1;
247 	nanoseconds = 200000000;
248 
249 	isc_interval_set(&interval, seconds, nanoseconds);
250 
251 	setup_test(isc_timertype_once, &interval, test_idle);
252 }
253 
254 /* timer reset */
255 static void
256 test_reset(void *arg) {
257 	isc_result_t result;
258 	isc_time_t now;
259 	isc_time_t base;
260 	isc_time_t ulim;
261 	isc_time_t llim;
262 	isc_interval_t interval;
263 	int tick = atomic_fetch_add(&eventcnt, 1);
264 
265 	UNUSED(arg);
266 
267 	if (verbose) {
268 		print_message("# tick %d\n", tick);
269 	}
270 
271 	/*
272 	 * Check expired time.
273 	 */
274 
275 	now = isc_time_now();
276 
277 	isc_interval_set(&interval, seconds, nanoseconds);
278 	isc_mutex_lock(&lasttime_mx);
279 	result = isc_time_add(&lasttime, &interval, &base);
280 	isc_mutex_unlock(&lasttime_mx);
281 	subthread_assert_result_equal(result, ISC_R_SUCCESS);
282 
283 	isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
284 	result = isc_time_add(&base, &interval, &ulim);
285 	subthread_assert_result_equal(result, ISC_R_SUCCESS);
286 
287 	result = isc_time_subtract(&base, &interval, &llim);
288 	subthread_assert_result_equal(result, ISC_R_SUCCESS);
289 
290 	subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
291 	subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
292 
293 	isc_interval_set(&interval, 0, 0);
294 	isc_mutex_lock(&lasttime_mx);
295 	isc_time_add(&now, &interval, &lasttime);
296 	isc_mutex_unlock(&lasttime_mx);
297 
298 	if (tick < 2) {
299 		if (tick == 1) {
300 			isc_interval_set(&interval, seconds, nanoseconds);
301 			isc_timer_start(timer, isc_timertype_once, &interval);
302 		}
303 	} else {
304 		isc_timer_destroy(&timer);
305 		isc_loopmgr_shutdown(loopmgr);
306 	}
307 }
308 
309 ISC_RUN_TEST_IMPL(reset) {
310 	isc_interval_t interval;
311 
312 	UNUSED(state);
313 
314 	nevents = 3;
315 	seconds = 0;
316 	nanoseconds = 750000000;
317 
318 	isc_interval_set(&interval, seconds, nanoseconds);
319 
320 	setup_test(isc_timertype_ticker, &interval, test_reset);
321 }
322 
323 static atomic_bool startflag;
324 static isc_timer_t *tickertimer = NULL;
325 static isc_timer_t *oncetimer = NULL;
326 
327 static void
328 tick_event(void *arg) {
329 	int tick;
330 
331 	UNUSED(arg);
332 
333 	if (!atomic_load(&startflag)) {
334 		if (verbose) {
335 			print_message("# tick_event %d\n", -1);
336 		}
337 		return;
338 	}
339 
340 	tick = atomic_fetch_add(&eventcnt, 1);
341 	if (verbose) {
342 		print_message("# tick_event %d\n", tick);
343 	}
344 
345 	/*
346 	 * On the first tick, purge all remaining tick events.
347 	 */
348 	if (tick == 0) {
349 		isc_timer_destroy(&tickertimer);
350 		isc_loopmgr_shutdown(loopmgr);
351 	}
352 }
353 
354 static void
355 once_event(void *arg) {
356 	UNUSED(arg);
357 
358 	if (verbose) {
359 		print_message("# once_event\n");
360 	}
361 
362 	/*
363 	 * Allow task1 to start processing events.
364 	 */
365 	atomic_store(&startflag, true);
366 
367 	isc_timer_destroy(&oncetimer);
368 }
369 
370 ISC_LOOP_SETUP_IMPL(purge) {
371 	atomic_init(&eventcnt, 0);
372 	atomic_store(&errcnt, ISC_R_SUCCESS);
373 }
374 
375 ISC_LOOP_TEARDOWN_IMPL(purge) {
376 	assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
377 	assert_int_equal(atomic_load(&eventcnt), 1);
378 }
379 
380 /* timer events purged */
381 ISC_LOOP_TEST_SETUP_TEARDOWN_IMPL(purge) {
382 	isc_interval_t interval;
383 
384 	UNUSED(arg);
385 
386 	if (verbose) {
387 		print_message("# purge_run\n");
388 	}
389 
390 	atomic_init(&startflag, 0);
391 	seconds = 1;
392 	nanoseconds = 0;
393 
394 	isc_interval_set(&interval, seconds, 0);
395 
396 	tickertimer = NULL;
397 	isc_timer_create(mainloop, tick_event, NULL, &tickertimer);
398 	isc_timer_start(tickertimer, isc_timertype_ticker, &interval);
399 
400 	oncetimer = NULL;
401 
402 	isc_interval_set(&interval, (seconds * 2) + 1, 0);
403 
404 	isc_timer_create(mainloop, once_event, NULL, &oncetimer);
405 	isc_timer_start(oncetimer, isc_timertype_once, &interval);
406 }
407 
408 /*
409  * Set of tests that check whether the rescheduling works as expected.
410  */
411 
412 isc_time_t timer_start;
413 isc_time_t timer_stop;
414 uint64_t timer_expect;
415 uint64_t timer_ticks;
416 isc_interval_t timer_interval;
417 isc_timertype_t timer_type;
418 
419 ISC_LOOP_TEARDOWN_IMPL(timer_expect) {
420 	uint64_t diff = isc_time_microdiff(&timer_stop, &timer_start) / 1000000;
421 	assert_true(diff == timer_expect);
422 }
423 
424 static void
425 timer_event(void *arg ISC_ATTR_UNUSED) {
426 	if (--timer_ticks == 0) {
427 		isc_timer_destroy(&timer);
428 		isc_loopmgr_shutdown(loopmgr);
429 		timer_stop = isc_loop_now(isc_loop());
430 	} else {
431 		isc_timer_start(timer, timer_type, &timer_interval);
432 	}
433 }
434 
435 ISC_LOOP_SETUP_IMPL(reschedule_up) {
436 	timer_start = isc_loop_now(isc_loop());
437 	timer_expect = 1;
438 	timer_ticks = 1;
439 	timer_type = isc_timertype_once;
440 }
441 
442 ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_up, setup_loop_reschedule_up,
443 			  teardown_loop_timer_expect) {
444 	isc_timer_create(mainloop, timer_event, NULL, &timer);
445 
446 	/* Schedule the timer to fire immediately */
447 	isc_interval_set(&timer_interval, 0, 0);
448 	isc_timer_start(timer, timer_type, &timer_interval);
449 
450 	/* And then reschedule it to 1 second */
451 	isc_interval_set(&timer_interval, 1, 0);
452 	isc_timer_start(timer, timer_type, &timer_interval);
453 }
454 
455 ISC_LOOP_SETUP_IMPL(reschedule_down) {
456 	timer_start = isc_loop_now(isc_loop());
457 	timer_expect = 0;
458 	timer_ticks = 1;
459 	timer_type = isc_timertype_once;
460 }
461 
462 ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_down, setup_loop_reschedule_down,
463 			  teardown_loop_timer_expect) {
464 	isc_timer_create(mainloop, timer_event, NULL, &timer);
465 
466 	/* Schedule the timer to fire at 10 seconds */
467 	isc_interval_set(&timer_interval, 10, 0);
468 	isc_timer_start(timer, timer_type, &timer_interval);
469 
470 	/* And then reschedule it fire immediately */
471 	isc_interval_set(&timer_interval, 0, 0);
472 	isc_timer_start(timer, timer_type, &timer_interval);
473 }
474 
475 ISC_LOOP_SETUP_IMPL(reschedule_from_callback) {
476 	timer_start = isc_loop_now(isc_loop());
477 	timer_expect = 1;
478 	timer_ticks = 2;
479 	timer_type = isc_timertype_once;
480 }
481 
482 ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_from_callback,
483 			  setup_loop_reschedule_from_callback,
484 			  teardown_loop_timer_expect) {
485 	isc_timer_create(mainloop, timer_event, NULL, &timer);
486 
487 	isc_interval_set(&timer_interval, 0, NS_PER_SEC / 2);
488 	isc_timer_start(timer, timer_type, &timer_interval);
489 }
490 
491 ISC_LOOP_SETUP_IMPL(zero) {
492 	timer_start = isc_loop_now(isc_loop());
493 	timer_expect = 0;
494 	timer_ticks = 1;
495 	timer_type = isc_timertype_once;
496 }
497 
498 ISC_LOOP_TEST_CUSTOM_IMPL(zero, setup_loop_zero, teardown_loop_timer_expect) {
499 	isc_timer_create(mainloop, timer_event, NULL, &timer);
500 
501 	/* Schedule the timer to fire immediately (in the next event loop) */
502 	isc_interval_set(&timer_interval, 0, 0);
503 	isc_timer_start(timer, timer_type, &timer_interval);
504 }
505 
506 ISC_LOOP_SETUP_IMPL(reschedule_ticker) {
507 	timer_start = isc_loop_now(isc_loop());
508 	timer_expect = 1;
509 	timer_ticks = 5;
510 	timer_type = isc_timertype_ticker;
511 }
512 
513 ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_ticker, setup_loop_reschedule_ticker,
514 			  teardown_loop_timer_expect) {
515 	isc_timer_create(mainloop, timer_event, NULL, &timer);
516 
517 	/* Schedule the timer to fire immediately (in the next event loop) */
518 	isc_interval_set(&timer_interval, 0, 0);
519 	isc_timer_start(timer, timer_type, &timer_interval);
520 
521 	/* Then fire every 1/4 second */
522 	isc_interval_set(&timer_interval, 0, NS_PER_SEC / 4);
523 }
524 
525 ISC_TEST_LIST_START
526 
527 ISC_TEST_ENTRY_CUSTOM(ticker, setup_loopmgr, teardown_loopmgr)
528 ISC_TEST_ENTRY_CUSTOM(once_idle, setup_loopmgr, teardown_loopmgr)
529 ISC_TEST_ENTRY_CUSTOM(reset, setup_loopmgr, teardown_loopmgr)
530 ISC_TEST_ENTRY_CUSTOM(purge, setup_loopmgr, teardown_loopmgr)
531 ISC_TEST_ENTRY_CUSTOM(reschedule_up, setup_loopmgr, teardown_loopmgr)
532 ISC_TEST_ENTRY_CUSTOM(reschedule_down, setup_loopmgr, teardown_loopmgr)
533 ISC_TEST_ENTRY_CUSTOM(reschedule_from_callback, setup_loopmgr, teardown_loopmgr)
534 ISC_TEST_ENTRY_CUSTOM(zero, setup_loopmgr, teardown_loopmgr)
535 ISC_TEST_ENTRY_CUSTOM(reschedule_ticker, setup_loopmgr, teardown_loopmgr)
536 
537 ISC_TEST_LIST_END
538 
539 ISC_TEST_MAIN
540