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