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