Home | History | Annotate | Line # | Download | only in isc
      1 /*	$NetBSD: rwlock_test.c,v 1.3 2025/05/21 14:48:08 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 <fcntl.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 <unistd.h>
     23 
     24 #define UNIT_TESTING
     25 #include <cmocka.h>
     26 
     27 #include <isc/atomic.h>
     28 #include <isc/barrier.h>
     29 #include <isc/file.h>
     30 #include <isc/mem.h>
     31 #include <isc/os.h>
     32 #include <isc/pause.h>
     33 #include <isc/random.h>
     34 #include <isc/result.h>
     35 #include <isc/rwlock.h>
     36 #include <isc/stdio.h>
     37 #include <isc/thread.h>
     38 #include <isc/time.h>
     39 #include <isc/util.h>
     40 
     41 #include <tests/isc.h>
     42 
     43 static unsigned int loops = 100;
     44 static unsigned int delay_loop = 1;
     45 
     46 static isc_rwlock_t rwlock;
     47 static pthread_rwlock_t prwlock;
     48 static isc_barrier_t barrier1;
     49 static isc_barrier_t barrier2;
     50 
     51 #define ITERS 20
     52 
     53 #define DC	200
     54 #define CNT_MIN 800
     55 #define CNT_MAX 1600
     56 
     57 static size_t shared_counter = 0;
     58 static size_t expected_counter = SIZE_MAX;
     59 static uint8_t boundary = 0;
     60 static uint8_t *rnd;
     61 
     62 static int
     63 setup_env(void **unused __attribute__((__unused__))) {
     64 	char *env = getenv("ISC_BENCHMARK_LOOPS");
     65 	if (env != NULL) {
     66 		loops = atoi(env);
     67 	}
     68 	assert_int_not_equal(loops, 0);
     69 
     70 	env = getenv("ISC_BENCHMARK_DELAY");
     71 	if (env != NULL) {
     72 		delay_loop = atoi(env);
     73 	}
     74 	assert_int_not_equal(delay_loop, 0);
     75 
     76 	rnd = isc_mem_cget(mctx, loops, sizeof(rnd[0]));
     77 	for (size_t i = 0; i < loops; i++) {
     78 		rnd[i] = (uint8_t)isc_random_uniform(100);
     79 	}
     80 
     81 	return 0;
     82 }
     83 
     84 static int
     85 teardown_env(void **state __attribute__((__unused__))) {
     86 	isc_mem_cput(mctx, rnd, loops, sizeof(rnd[0]));
     87 
     88 	return 0;
     89 }
     90 
     91 static int
     92 rwlock_setup(void **state __attribute__((__unused__))) {
     93 	isc_rwlock_init(&rwlock);
     94 
     95 	isc_barrier_init(&barrier1, 2);
     96 	isc_barrier_init(&barrier2, 2);
     97 	if (pthread_rwlock_init(&prwlock, NULL) == -1) {
     98 		return errno;
     99 	}
    100 
    101 	return 0;
    102 }
    103 
    104 static int
    105 rwlock_teardown(void **state __attribute__((__unused__))) {
    106 	if (pthread_rwlock_destroy(&prwlock) == -1) {
    107 		return errno;
    108 	}
    109 	isc_barrier_destroy(&barrier2);
    110 	isc_barrier_destroy(&barrier1);
    111 
    112 	isc_rwlock_destroy(&rwlock);
    113 
    114 	return 0;
    115 }
    116 
    117 /*
    118  * Simple single-threaded read lock/unlock test
    119  */
    120 ISC_RUN_TEST_IMPL(isc_rwlock_rdlock) {
    121 	isc_rwlock_lock(&rwlock, isc_rwlocktype_read);
    122 	isc_pause_n(delay_loop);
    123 	isc_rwlock_unlock(&rwlock, isc_rwlocktype_read);
    124 }
    125 
    126 /*
    127  * Simple single-threaded write lock/unlock test
    128  */
    129 ISC_RUN_TEST_IMPL(isc_rwlock_wrlock) {
    130 	isc_rwlock_lock(&rwlock, isc_rwlocktype_write);
    131 	isc_pause_n(delay_loop);
    132 	isc_rwlock_unlock(&rwlock, isc_rwlocktype_write);
    133 }
    134 
    135 /*
    136  * Simple single-threaded lock/tryupgrade/unlock test
    137  */
    138 ISC_RUN_TEST_IMPL(isc_rwlock_tryupgrade) {
    139 	isc_result_t result;
    140 	isc_rwlock_lock(&rwlock, isc_rwlocktype_read);
    141 	result = isc_rwlock_tryupgrade(&rwlock);
    142 #if USE_PTHREAD_RWLOCK
    143 	/*
    144 	 * Our pthread-based rwlock implementation does not support tryupgrade,
    145 	 * and always returns ISC_R_LOCKBUSY.
    146 	 */
    147 	assert_int_equal(result, ISC_R_LOCKBUSY);
    148 	isc_rwlock_unlock(&rwlock, isc_rwlocktype_read);
    149 #else
    150 	assert_int_equal(result, ISC_R_SUCCESS);
    151 	isc_rwlock_unlock(&rwlock, isc_rwlocktype_write);
    152 #endif /* USE_PTHREAD_RWLOCK */
    153 }
    154 
    155 static void *
    156 trylock_thread1(void *arg __attribute__((__unused__))) {
    157 	isc_rwlock_lock(&rwlock, isc_rwlocktype_write);
    158 
    159 	isc_barrier_wait(&barrier1);
    160 	isc_barrier_wait(&barrier2);
    161 
    162 	isc_rwlock_unlock(&rwlock, isc_rwlocktype_write);
    163 
    164 	isc_rwlock_lock(&rwlock, isc_rwlocktype_read);
    165 
    166 	isc_barrier_wait(&barrier1);
    167 	isc_barrier_wait(&barrier2);
    168 
    169 	isc_rwlock_unlock(&rwlock, isc_rwlocktype_read);
    170 
    171 	return NULL;
    172 }
    173 
    174 static void *
    175 trylock_thread2(void *arg __attribute__((__unused__))) {
    176 	isc_result_t result;
    177 
    178 	isc_barrier_wait(&barrier1);
    179 
    180 	result = isc_rwlock_trylock(&rwlock, isc_rwlocktype_read);
    181 	assert_int_equal(result, ISC_R_LOCKBUSY);
    182 
    183 	isc_barrier_wait(&barrier2);
    184 	isc_barrier_wait(&barrier1);
    185 
    186 	result = isc_rwlock_trylock(&rwlock, isc_rwlocktype_read);
    187 	assert_int_equal(result, ISC_R_SUCCESS);
    188 
    189 	isc_barrier_wait(&barrier2);
    190 
    191 	isc_rwlock_unlock(&rwlock, isc_rwlocktype_read);
    192 
    193 	return NULL;
    194 }
    195 
    196 ISC_RUN_TEST_IMPL(isc_rwlock_trylock) {
    197 	isc_thread_t thread1;
    198 	isc_thread_t thread2;
    199 
    200 	isc_thread_create(trylock_thread1, NULL, &thread1);
    201 	isc_thread_create(trylock_thread2, NULL, &thread2);
    202 
    203 	isc_thread_join(thread2, NULL);
    204 	isc_thread_join(thread1, NULL);
    205 }
    206 
    207 static void *
    208 pthread_rwlock_thread(void *arg __attribute__((__unused__))) {
    209 	size_t cont = *(size_t *)arg;
    210 
    211 	for (size_t i = 0; i < loops; i++) {
    212 		if (rnd[i] < boundary) {
    213 			pthread_rwlock_wrlock(&prwlock);
    214 			size_t v = shared_counter;
    215 			isc_pause_n(delay_loop);
    216 			shared_counter = v + 1;
    217 			pthread_rwlock_unlock(&prwlock);
    218 		} else {
    219 			pthread_rwlock_rdlock(&prwlock);
    220 			isc_pause_n(delay_loop);
    221 			pthread_rwlock_unlock(&prwlock);
    222 		}
    223 		isc_pause_n(cont);
    224 	}
    225 
    226 	return NULL;
    227 }
    228 
    229 static void *
    230 isc_rwlock_thread(void *arg __attribute__((__unused__))) {
    231 	size_t cont = *(size_t *)arg;
    232 
    233 	for (size_t i = 0; i < loops; i++) {
    234 		if (rnd[i] < boundary) {
    235 			isc_rwlock_lock(&rwlock, isc_rwlocktype_write);
    236 			size_t v = shared_counter;
    237 			isc_pause_n(delay_loop);
    238 			shared_counter = v + 1;
    239 			isc_rwlock_unlock(&rwlock, isc_rwlocktype_write);
    240 		} else {
    241 			isc_rwlock_lock(&rwlock, isc_rwlocktype_read);
    242 			isc_pause_n(delay_loop);
    243 			isc_rwlock_unlock(&rwlock, isc_rwlocktype_read);
    244 		}
    245 		isc_pause_n(cont);
    246 	}
    247 
    248 	return NULL;
    249 }
    250 
    251 static void
    252 isc__rwlock_benchmark(isc_thread_t *threads, unsigned int nthreads,
    253 		      uint8_t pct) {
    254 	isc_time_t ts1, ts2;
    255 	double t;
    256 	int dc;
    257 	size_t cont;
    258 
    259 	expected_counter = ITERS * nthreads * loops *
    260 			   ((CNT_MAX - CNT_MIN) / DC + 1);
    261 
    262 	boundary = pct;
    263 
    264 	/* PTHREAD RWLOCK */
    265 
    266 	ts1 = isc_time_now_hires();
    267 
    268 	shared_counter = 0;
    269 	dc = DC;
    270 	for (size_t l = 0; l < ITERS; l++) {
    271 		for (cont = (dc > 0) ? CNT_MIN : CNT_MAX;
    272 		     cont <= CNT_MAX && cont >= CNT_MIN; cont += dc)
    273 		{
    274 			for (size_t i = 0; i < nthreads; i++) {
    275 				isc_thread_create(pthread_rwlock_thread, &cont,
    276 						  &threads[i]);
    277 			}
    278 			for (size_t i = 0; i < nthreads; i++) {
    279 				isc_thread_join(threads[i], NULL);
    280 			}
    281 		}
    282 		dc = -dc;
    283 	}
    284 
    285 	ts2 = isc_time_now_hires();
    286 
    287 	t = isc_time_microdiff(&ts2, &ts1);
    288 
    289 	printf("[ TIME     ] isc_rwlock_benchmark: %zu pthread_rwlock loops in "
    290 	       "%u threads, %2.3f%% writes, %2.3f seconds, %2.3f "
    291 	       "calls/second\n",
    292 	       expected_counter, nthreads,
    293 	       (double)shared_counter * 100 / expected_counter, t / 1000000.0,
    294 	       expected_counter / (t / 1000000.0));
    295 
    296 	/* ISC RWLOCK */
    297 
    298 	ts1 = isc_time_now_hires();
    299 
    300 	dc = DC;
    301 	shared_counter = 0;
    302 	for (size_t l = 0; l < ITERS; l++) {
    303 		for (cont = (dc > 0) ? CNT_MIN : CNT_MAX;
    304 		     cont <= CNT_MAX && cont >= CNT_MIN; cont += dc)
    305 		{
    306 			for (size_t i = 0; i < nthreads; i++) {
    307 				isc_thread_create(isc_rwlock_thread, &cont,
    308 						  &threads[i]);
    309 			}
    310 			for (size_t i = 0; i < nthreads; i++) {
    311 				isc_thread_join(threads[i], NULL);
    312 			}
    313 		}
    314 		dc = -dc;
    315 	}
    316 
    317 	ts2 = isc_time_now_hires();
    318 
    319 	t = isc_time_microdiff(&ts2, &ts1);
    320 
    321 	printf("[ TIME     ] isc_rwlock_benchmark: %zu isc_rwlock loops in "
    322 	       "%u threads, %2.3f%% writes, %2.3f seconds, %2.3f "
    323 	       "calls/second\n",
    324 	       expected_counter, nthreads,
    325 	       (double)shared_counter * 100 / expected_counter, t / 1000000.0,
    326 	       expected_counter / (t / 1000000.0));
    327 }
    328 
    329 ISC_RUN_TEST_IMPL(isc_rwlock_benchmark) {
    330 	isc_thread_t *threads = isc_mem_cget(mctx, workers, sizeof(*threads));
    331 
    332 	memset(threads, 0, sizeof(*threads) * workers);
    333 
    334 	for (unsigned int nthreads = workers; nthreads > 0; nthreads /= 2) {
    335 		isc__rwlock_benchmark(threads, nthreads, 0);
    336 		isc__rwlock_benchmark(threads, nthreads, 1);
    337 		isc__rwlock_benchmark(threads, nthreads, 10);
    338 		isc__rwlock_benchmark(threads, nthreads, 50);
    339 		isc__rwlock_benchmark(threads, nthreads, 90);
    340 		isc__rwlock_benchmark(threads, nthreads, 99);
    341 		isc__rwlock_benchmark(threads, nthreads, 100);
    342 	}
    343 
    344 	isc_mem_cput(mctx, threads, workers, sizeof(*threads));
    345 }
    346 
    347 ISC_TEST_LIST_START
    348 
    349 ISC_TEST_ENTRY_CUSTOM(isc_rwlock_rdlock, rwlock_setup, rwlock_teardown)
    350 ISC_TEST_ENTRY_CUSTOM(isc_rwlock_wrlock, rwlock_setup, rwlock_teardown)
    351 #if !defined(__SANITIZE_THREAD__)
    352 ISC_TEST_ENTRY_CUSTOM(isc_rwlock_tryupgrade, rwlock_setup, rwlock_teardown)
    353 ISC_TEST_ENTRY_CUSTOM(isc_rwlock_trylock, rwlock_setup, rwlock_teardown)
    354 ISC_TEST_ENTRY_CUSTOM(isc_rwlock_benchmark, rwlock_setup, rwlock_teardown)
    355 #endif /* __SANITIZE_THREAD__ */
    356 
    357 ISC_TEST_LIST_END
    358 
    359 ISC_TEST_MAIN_CUSTOM(setup_env, teardown_env)
    360