Home | History | Annotate | Line # | Download | only in isc
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