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