timer_test.c revision 1.2.4.2 1 /* $NetBSD: timer_test.c,v 1.2.4.2 2024/02/29 12:35:57 martin 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/mem.h>
32 #include <isc/print.h>
33 #include <isc/task.h>
34 #include <isc/time.h>
35 #include <isc/timer.h>
36 #include <isc/util.h>
37
38 #include "netmgr/uv-compat.h"
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 = false;
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_condition_t cv;
51 static isc_mutex_t mx;
52 static isc_time_t endtime;
53 static isc_mutex_t lasttime_mx;
54 static isc_time_t lasttime;
55 static int seconds;
56 static int nanoseconds;
57 static atomic_int_fast32_t eventcnt;
58 static atomic_uint_fast32_t errcnt;
59 static int nevents;
60
61 static int
62 _setup(void **state) {
63 atomic_init(&errcnt, ISC_R_SUCCESS);
64
65 setup_managers(state);
66
67 return (0);
68 }
69
70 static int
71 _teardown(void **state) {
72 teardown_managers(state);
73
74 return (0);
75 }
76
77 static void
78 test_shutdown(isc_task_t *task, isc_event_t *event) {
79 isc_result_t result;
80
81 UNUSED(task);
82
83 /*
84 * Signal shutdown processing complete.
85 */
86 result = isc_mutex_lock(&mx);
87 assert_int_equal(result, ISC_R_SUCCESS);
88
89 result = isc_condition_signal(&cv);
90 assert_int_equal(result, ISC_R_SUCCESS);
91
92 result = isc_mutex_unlock(&mx);
93 assert_int_equal(result, ISC_R_SUCCESS);
94
95 isc_event_free(&event);
96 }
97
98 static void
99 setup_test(isc_timertype_t timertype, isc_time_t *expires,
100 isc_interval_t *interval,
101 void (*action)(isc_task_t *, isc_event_t *)) {
102 isc_result_t result;
103 isc_task_t *task = NULL;
104 isc_time_settoepoch(&endtime);
105 atomic_init(&eventcnt, 0);
106
107 isc_mutex_init(&mx);
108 isc_mutex_init(&lasttime_mx);
109
110 isc_condition_init(&cv);
111
112 atomic_store(&errcnt, ISC_R_SUCCESS);
113
114 LOCK(&mx);
115
116 result = isc_task_create(taskmgr, 0, &task);
117 assert_int_equal(result, ISC_R_SUCCESS);
118
119 result = isc_task_onshutdown(task, test_shutdown, NULL);
120 assert_int_equal(result, ISC_R_SUCCESS);
121
122 isc_mutex_lock(&lasttime_mx);
123 result = isc_time_now(&lasttime);
124 isc_mutex_unlock(&lasttime_mx);
125 assert_int_equal(result, ISC_R_SUCCESS);
126
127 result = isc_timer_create(timermgr, timertype, expires, interval, task,
128 action, (void *)timertype, &timer);
129 assert_int_equal(result, ISC_R_SUCCESS);
130
131 /*
132 * Wait for shutdown processing to complete.
133 */
134 while (atomic_load(&eventcnt) != nevents) {
135 result = isc_condition_wait(&cv, &mx);
136 assert_int_equal(result, ISC_R_SUCCESS);
137 }
138
139 UNLOCK(&mx);
140
141 assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
142
143 isc_task_detach(&task);
144 isc_mutex_destroy(&mx);
145 isc_mutex_destroy(&lasttime_mx);
146 (void)isc_condition_destroy(&cv);
147 }
148
149 static void
150 set_global_error(isc_result_t result) {
151 (void)atomic_compare_exchange_strong(
152 &errcnt, &(uint_fast32_t){ ISC_R_SUCCESS }, result);
153 }
154
155 static void
156 subthread_assert_true(bool expected, const char *file, unsigned int line) {
157 if (!expected) {
158 printf("# %s:%u subthread_assert_true\n", file, line);
159 set_global_error(ISC_R_UNEXPECTED);
160 }
161 }
162 #define subthread_assert_true(expected) \
163 subthread_assert_true(expected, __FILE__, __LINE__)
164
165 static void
166 subthread_assert_int_equal(int observed, int expected, const char *file,
167 unsigned int line) {
168 if (observed != expected) {
169 printf("# %s:%u subthread_assert_int_equal(%d != %d)\n", file,
170 line, observed, expected);
171 set_global_error(ISC_R_UNEXPECTED);
172 }
173 }
174 #define subthread_assert_int_equal(observed, expected) \
175 subthread_assert_int_equal(observed, expected, __FILE__, __LINE__)
176
177 static void
178 subthread_assert_result_equal(isc_result_t result, isc_result_t expected,
179 const char *file, unsigned int line) {
180 if (result != expected) {
181 printf("# %s:%u subthread_assert_result_equal(%u != %u)\n",
182 file, line, (unsigned int)result,
183 (unsigned int)expected);
184 set_global_error(result);
185 }
186 }
187 #define subthread_assert_result_equal(observed, expected) \
188 subthread_assert_result_equal(observed, expected, __FILE__, __LINE__)
189
190 static void
191 ticktock(isc_task_t *task, isc_event_t *event) {
192 isc_result_t result;
193 isc_time_t now;
194 isc_time_t base;
195 isc_time_t ulim;
196 isc_time_t llim;
197 isc_interval_t interval;
198 isc_eventtype_t expected_event_type;
199
200 int tick = atomic_fetch_add(&eventcnt, 1);
201
202 if (verbose) {
203 print_message("# tick %d\n", tick);
204 }
205
206 expected_event_type = ISC_TIMEREVENT_LIFE;
207 if ((uintptr_t)event->ev_arg == isc_timertype_ticker) {
208 expected_event_type = ISC_TIMEREVENT_TICK;
209 }
210
211 if (event->ev_type != expected_event_type) {
212 print_error("# expected event type %u, got %u\n",
213 expected_event_type, event->ev_type);
214 }
215
216 result = isc_time_now(&now);
217 subthread_assert_result_equal(result, ISC_R_SUCCESS);
218
219 isc_interval_set(&interval, seconds, nanoseconds);
220 isc_mutex_lock(&lasttime_mx);
221 result = isc_time_add(&lasttime, &interval, &base);
222 isc_mutex_unlock(&lasttime_mx);
223 subthread_assert_result_equal(result, ISC_R_SUCCESS);
224
225 isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
226 result = isc_time_add(&base, &interval, &ulim);
227 subthread_assert_result_equal(result, ISC_R_SUCCESS);
228
229 result = isc_time_subtract(&base, &interval, &llim);
230 subthread_assert_result_equal(result, ISC_R_SUCCESS);
231
232 subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
233 subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
234
235 isc_interval_set(&interval, 0, 0);
236 isc_mutex_lock(&lasttime_mx);
237 result = isc_time_add(&now, &interval, &lasttime);
238 isc_mutex_unlock(&lasttime_mx);
239 subthread_assert_result_equal(result, ISC_R_SUCCESS);
240
241 isc_event_free(&event);
242
243 if (atomic_load(&eventcnt) == nevents) {
244 result = isc_time_now(&endtime);
245 subthread_assert_result_equal(result, ISC_R_SUCCESS);
246 isc_timer_destroy(&timer);
247 isc_task_shutdown(task);
248 }
249 }
250
251 /*
252 * Individual unit tests
253 */
254
255 /* timer type ticker */
256 ISC_RUN_TEST_IMPL(ticker) {
257 isc_time_t expires;
258 isc_interval_t interval;
259
260 UNUSED(state);
261
262 nevents = 12;
263 seconds = 0;
264 nanoseconds = 500000000;
265
266 isc_interval_set(&interval, seconds, nanoseconds);
267 isc_time_settoepoch(&expires);
268
269 setup_test(isc_timertype_ticker, &expires, &interval, ticktock);
270 }
271
272 /* timer type once reaches lifetime */
273 ISC_RUN_TEST_IMPL(once_life) {
274 isc_result_t result;
275 isc_time_t expires;
276 isc_interval_t interval;
277
278 UNUSED(state);
279
280 nevents = 1;
281 seconds = 1;
282 nanoseconds = 100000000;
283
284 isc_interval_set(&interval, seconds, nanoseconds);
285 result = isc_time_nowplusinterval(&expires, &interval);
286 assert_int_equal(result, ISC_R_SUCCESS);
287
288 isc_interval_set(&interval, 0, 0);
289
290 setup_test(isc_timertype_once, &expires, &interval, ticktock);
291 }
292
293 static void
294 test_idle(isc_task_t *task, isc_event_t *event) {
295 isc_result_t result;
296 isc_time_t now;
297 isc_time_t base;
298 isc_time_t ulim;
299 isc_time_t llim;
300 isc_interval_t interval;
301
302 int tick = atomic_fetch_add(&eventcnt, 1);
303
304 if (verbose) {
305 print_message("# tick %d\n", tick);
306 }
307
308 result = isc_time_now(&now);
309 subthread_assert_result_equal(result, ISC_R_SUCCESS);
310
311 isc_interval_set(&interval, seconds, nanoseconds);
312 isc_mutex_lock(&lasttime_mx);
313 result = isc_time_add(&lasttime, &interval, &base);
314 isc_mutex_unlock(&lasttime_mx);
315 subthread_assert_result_equal(result, ISC_R_SUCCESS);
316
317 isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
318 result = isc_time_add(&base, &interval, &ulim);
319 subthread_assert_result_equal(result, ISC_R_SUCCESS);
320
321 result = isc_time_subtract(&base, &interval, &llim);
322 subthread_assert_result_equal(result, ISC_R_SUCCESS);
323
324 subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
325 subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
326
327 isc_interval_set(&interval, 0, 0);
328 isc_mutex_lock(&lasttime_mx);
329 isc_time_add(&now, &interval, &lasttime);
330 isc_mutex_unlock(&lasttime_mx);
331
332 subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_IDLE);
333
334 isc_event_free(&event);
335
336 isc_timer_destroy(&timer);
337 isc_task_shutdown(task);
338 }
339
340 /* timer type once idles out */
341 ISC_RUN_TEST_IMPL(once_idle) {
342 isc_result_t result;
343 isc_time_t expires;
344 isc_interval_t interval;
345
346 UNUSED(state);
347
348 nevents = 1;
349 seconds = 1;
350 nanoseconds = 200000000;
351
352 isc_interval_set(&interval, seconds + 1, nanoseconds);
353 result = isc_time_nowplusinterval(&expires, &interval);
354 assert_int_equal(result, ISC_R_SUCCESS);
355
356 isc_interval_set(&interval, seconds, nanoseconds);
357
358 setup_test(isc_timertype_once, &expires, &interval, test_idle);
359 }
360
361 /* timer reset */
362 static void
363 test_reset(isc_task_t *task, isc_event_t *event) {
364 isc_result_t result;
365 isc_time_t now;
366 isc_time_t base;
367 isc_time_t ulim;
368 isc_time_t llim;
369 isc_time_t expires;
370 isc_interval_t interval;
371
372 int tick = atomic_fetch_add(&eventcnt, 1);
373
374 if (verbose) {
375 print_message("# tick %d\n", tick);
376 }
377
378 /*
379 * Check expired time.
380 */
381
382 result = isc_time_now(&now);
383 subthread_assert_result_equal(result, ISC_R_SUCCESS);
384
385 isc_interval_set(&interval, seconds, nanoseconds);
386 isc_mutex_lock(&lasttime_mx);
387 result = isc_time_add(&lasttime, &interval, &base);
388 isc_mutex_unlock(&lasttime_mx);
389 subthread_assert_result_equal(result, ISC_R_SUCCESS);
390
391 isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
392 result = isc_time_add(&base, &interval, &ulim);
393 subthread_assert_result_equal(result, ISC_R_SUCCESS);
394
395 result = isc_time_subtract(&base, &interval, &llim);
396 subthread_assert_result_equal(result, ISC_R_SUCCESS);
397
398 subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
399 subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
400
401 isc_interval_set(&interval, 0, 0);
402 isc_mutex_lock(&lasttime_mx);
403 isc_time_add(&now, &interval, &lasttime);
404 isc_mutex_unlock(&lasttime_mx);
405
406 int _eventcnt = atomic_load(&eventcnt);
407
408 if (_eventcnt < 3) {
409 subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_TICK);
410
411 if (_eventcnt == 2) {
412 isc_interval_set(&interval, seconds, nanoseconds);
413 result = isc_time_nowplusinterval(&expires, &interval);
414 subthread_assert_result_equal(result, ISC_R_SUCCESS);
415
416 isc_interval_set(&interval, 0, 0);
417 result = isc_timer_reset(timer, isc_timertype_once,
418 &expires, &interval, false);
419 subthread_assert_result_equal(result, ISC_R_SUCCESS);
420 }
421
422 isc_event_free(&event);
423 } else {
424 subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_LIFE);
425
426 isc_event_free(&event);
427 isc_timer_destroy(&timer);
428 isc_task_shutdown(task);
429 }
430 }
431
432 ISC_RUN_TEST_IMPL(reset) {
433 isc_time_t expires;
434 isc_interval_t interval;
435
436 UNUSED(state);
437
438 nevents = 3;
439 seconds = 0;
440 nanoseconds = 750000000;
441
442 isc_interval_set(&interval, seconds, nanoseconds);
443 isc_time_settoepoch(&expires);
444
445 setup_test(isc_timertype_ticker, &expires, &interval, test_reset);
446 }
447
448 static atomic_bool startflag;
449 static atomic_bool shutdownflag;
450 static isc_timer_t *tickertimer = NULL;
451 static isc_timer_t *oncetimer = NULL;
452 static isc_task_t *task1 = NULL;
453 static isc_task_t *task2 = NULL;
454
455 /*
456 * task1 blocks on mx while events accumulate
457 * in its queue, until signaled by task2.
458 */
459
460 static void
461 tick_event(isc_task_t *task, isc_event_t *event) {
462 isc_result_t result;
463 isc_time_t expires;
464 isc_interval_t interval;
465
466 UNUSED(task);
467
468 if (!atomic_load(&startflag)) {
469 if (verbose) {
470 print_message("# tick_event %d\n", -1);
471 }
472 isc_event_free(&event);
473 return;
474 }
475
476 int tick = atomic_fetch_add(&eventcnt, 1);
477 if (verbose) {
478 print_message("# tick_event %d\n", tick);
479 }
480
481 /*
482 * On the first tick, purge all remaining tick events
483 * and then shut down the task.
484 */
485 if (tick == 0) {
486 isc_time_settoepoch(&expires);
487 isc_interval_set(&interval, seconds, 0);
488 result = isc_timer_reset(tickertimer, isc_timertype_ticker,
489 &expires, &interval, true);
490 subthread_assert_result_equal(result, ISC_R_SUCCESS);
491
492 isc_task_shutdown(task);
493 }
494
495 isc_event_free(&event);
496 }
497
498 static void
499 once_event(isc_task_t *task, isc_event_t *event) {
500 if (verbose) {
501 print_message("# once_event\n");
502 }
503
504 /*
505 * Allow task1 to start processing events.
506 */
507 atomic_store(&startflag, true);
508
509 isc_event_free(&event);
510 isc_task_shutdown(task);
511 }
512
513 static void
514 shutdown_purge(isc_task_t *task, isc_event_t *event) {
515 UNUSED(task);
516 UNUSED(event);
517
518 if (verbose) {
519 print_message("# shutdown_event\n");
520 }
521
522 /*
523 * Signal shutdown processing complete.
524 */
525 atomic_store(&shutdownflag, 1);
526
527 isc_event_free(&event);
528 }
529
530 /* timer events purged */
531 ISC_RUN_TEST_IMPL(purge) {
532 isc_result_t result;
533 isc_time_t expires;
534 isc_interval_t interval;
535
536 UNUSED(state);
537
538 atomic_init(&startflag, 0);
539 atomic_init(&shutdownflag, 0);
540 atomic_init(&eventcnt, 0);
541 seconds = 1;
542 nanoseconds = 0;
543
544 result = isc_task_create(taskmgr, 0, &task1);
545 assert_int_equal(result, ISC_R_SUCCESS);
546
547 result = isc_task_onshutdown(task1, shutdown_purge, NULL);
548 assert_int_equal(result, ISC_R_SUCCESS);
549
550 result = isc_task_create(taskmgr, 0, &task2);
551 assert_int_equal(result, ISC_R_SUCCESS);
552
553 isc_time_settoepoch(&expires);
554 isc_interval_set(&interval, seconds, 0);
555
556 tickertimer = NULL;
557 result = isc_timer_create(timermgr, isc_timertype_ticker, &expires,
558 &interval, task1, tick_event, NULL,
559 &tickertimer);
560 assert_int_equal(result, ISC_R_SUCCESS);
561
562 oncetimer = NULL;
563
564 isc_interval_set(&interval, (seconds * 2) + 1, 0);
565 result = isc_time_nowplusinterval(&expires, &interval);
566 assert_int_equal(result, ISC_R_SUCCESS);
567
568 isc_interval_set(&interval, 0, 0);
569 result = isc_timer_create(timermgr, isc_timertype_once, &expires,
570 &interval, task2, once_event, NULL,
571 &oncetimer);
572 assert_int_equal(result, ISC_R_SUCCESS);
573
574 /*
575 * Wait for shutdown processing to complete.
576 */
577 while (!atomic_load(&shutdownflag)) {
578 uv_sleep(1);
579 }
580
581 assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
582
583 assert_int_equal(atomic_load(&eventcnt), 1);
584
585 isc_timer_destroy(&tickertimer);
586 isc_timer_destroy(&oncetimer);
587 isc_task_destroy(&task1);
588 isc_task_destroy(&task2);
589 }
590
591 ISC_TEST_LIST_START
592
593 ISC_TEST_ENTRY_CUSTOM(ticker, _setup, _teardown)
594 ISC_TEST_ENTRY_CUSTOM(once_life, _setup, _teardown)
595 ISC_TEST_ENTRY_CUSTOM(once_idle, _setup, _teardown)
596 ISC_TEST_ENTRY_CUSTOM(reset, _setup, _teardown)
597 ISC_TEST_ENTRY_CUSTOM(purge, _setup, _teardown)
598
599 ISC_TEST_LIST_END
600
601 ISC_TEST_MAIN
602