1/* $NetBSD: timer_test.c,v 1.2 2024/02/21 22:52:51 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/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 */ 44static 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 49static isc_timer_t *timer = NULL; 50static isc_condition_t cv; 51static isc_mutex_t mx; 52static isc_time_t endtime; 53static isc_mutex_t lasttime_mx; 54static isc_time_t lasttime; 55static int seconds; 56static int nanoseconds; 57static atomic_int_fast32_t eventcnt; 58static atomic_uint_fast32_t errcnt; 59static int nevents; 60 61static int 62_setup(void **state) { 63 atomic_init(&errcnt, ISC_R_SUCCESS); 64 65 setup_managers(state); 66 67 return (0); 68} 69 70static int 71_teardown(void **state) { 72 teardown_managers(state); 73 74 return (0); 75} 76 77static void 78test_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 98static void 99setup_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 149static void 150set_global_error(isc_result_t result) { 151 (void)atomic_compare_exchange_strong( 152 &errcnt, &(uint_fast32_t){ ISC_R_SUCCESS }, result); 153} 154 155static void 156subthread_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 165static void 166subthread_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 177static void 178subthread_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 190static void 191ticktock(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 */ 256ISC_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 */ 273ISC_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 293static void 294test_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 */ 341ISC_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 */ 362static void 363test_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 432ISC_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 448static atomic_bool startflag; 449static atomic_bool shutdownflag; 450static isc_timer_t *tickertimer = NULL; 451static isc_timer_t *oncetimer = NULL; 452static isc_task_t *task1 = NULL; 453static 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 460static void 461tick_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 498static void 499once_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 513static void 514shutdown_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 */ 531ISC_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 591ISC_TEST_LIST_START 592 593ISC_TEST_ENTRY_CUSTOM(ticker, _setup, _teardown) 594ISC_TEST_ENTRY_CUSTOM(once_life, _setup, _teardown) 595ISC_TEST_ENTRY_CUSTOM(once_idle, _setup, _teardown) 596ISC_TEST_ENTRY_CUSTOM(reset, _setup, _teardown) 597ISC_TEST_ENTRY_CUSTOM(purge, _setup, _teardown) 598 599ISC_TEST_LIST_END 600 601ISC_TEST_MAIN 602