1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2024 Oxide Computer Company 14 */ 15 16 /* 17 * mutex-specific tests 18 */ 19 20 #include <err.h> 21 #include <stdlib.h> 22 #include <pthread.h> 23 #include <sys/debug.h> 24 #include <sys/sysmacros.h> 25 #include <stdbool.h> 26 #include <errno.h> 27 #include <string.h> 28 29 #include "clock_lock.h" 30 31 static void 32 clock_mutex_create(const char *desc, void **argp) 33 { 34 int ret; 35 pthread_mutex_t *mtx; 36 pthread_mutexattr_t attr; 37 38 mtx = calloc(1, sizeof (pthread_mutex_t)); 39 if (mtx == NULL) { 40 err(EXIT_FAILURE, "TEST FAILED: %s: failed to allocate memory " 41 "for a mutex", desc); 42 } 43 44 if ((ret = pthread_mutexattr_init(&attr)) != 0) { 45 errc(EXIT_FAILURE, ret, "TEST FAILED: %s: failed to initialize " 46 "mutex attributes", desc); 47 } 48 49 if ((ret = pthread_mutexattr_settype(&attr, 50 PTHREAD_MUTEX_ERRORCHECK)) != 0) { 51 errc(EXIT_FAILURE, ret, "TEST FAILED: %s: failed to set mutex " 52 "type to error checking", desc); 53 } 54 55 if ((ret = pthread_mutex_init(mtx, &attr)) != 0) { 56 errc(EXIT_FAILURE, ret, "TEST FAILED: %s: failed to create " 57 "mutex", desc); 58 } 59 60 *argp = mtx; 61 } 62 63 static void 64 clock_mutex_destroy(void *arg) 65 { 66 VERIFY0(pthread_mutex_destroy(arg)); 67 free(arg); 68 } 69 70 static void 71 clock_mutex_lock(void *arg) 72 { 73 VERIFY0(pthread_mutex_trylock(arg)); 74 } 75 76 static void 77 clock_mutex_unlock(void *arg) 78 { 79 pthread_mutex_exit_np(arg); 80 } 81 82 const lock_ops_t clock_lock_mutex_ops = { 83 .lo_create = clock_mutex_create, 84 .lo_destroy = clock_mutex_destroy, 85 .lo_lock = clock_mutex_lock, 86 .lo_unlock = clock_mutex_unlock 87 }; 88 89 static bool 90 clock_test_mutex_invalid_source(const clock_test_t *test, void *prim) 91 { 92 bool ret = true; 93 pthread_mutex_t *mutex = prim; 94 const clockid_t clocks[] = { 0x7777, INT32_MAX, 0x23, CLOCK_VIRTUAL, 95 CLOCK_THREAD_CPUTIME_ID, CLOCK_PROCESS_CPUTIME_ID }; 96 int p; 97 98 for (size_t i = 0; i < ARRAY_SIZE(clocks); i++) { 99 clockid_t c = clocks[i]; 100 101 if ((p = pthread_mutex_clocklock(mutex, c, &clock_to_100ms)) != 102 EINVAL) { 103 warnx("TEST FAILED: %s: pthread_mutex_clocklock with " 104 "clock 0x%x returned %s, not EINVAL", test->ct_desc, 105 c, strerrorname_np(p)); 106 ret = false; 107 } 108 109 if ((p = pthread_mutex_relclocklock_np(mutex, c, 110 &clock_to_100ms)) != EINVAL) { 111 warnx("TEST FAILED: %s: pthread_mutex_relclocklock_np " 112 "with clock 0x%x returned %s, not EINVAL", 113 test->ct_desc, c, strerrorname_np(p)); 114 ret = false; 115 } 116 } 117 118 return (ret); 119 } 120 121 static bool 122 clock_test_mutex_inv_to_ign_abs(const clock_test_t *test, void *prim) 123 { 124 bool ret = true; 125 pthread_mutex_t *mutex = prim; 126 int p; 127 128 if ((p = pthread_mutex_timedlock(mutex, &clock_to_invns)) != 0) { 129 warnx("TEST FAILED: %s: pthread_mutex_timedlock failed with " 130 "an invalid timeout when the lock when lock was available: " 131 "expected success, found %s", test->ct_desc, 132 strerrorname_np(p)); 133 ret = false; 134 } else { 135 test->ct_ops->lo_unlock(mutex); 136 } 137 138 if ((p = pthread_mutex_clocklock(mutex, CLOCK_MONOTONIC, 139 &clock_to_invns)) != 0) { 140 warnx("TEST FAILED: %s: pthread_mutex_clocklock failed with " 141 "an invalid timeout when the lock when lock was available: " 142 "expected success, found %s", test->ct_desc, 143 strerrorname_np(p)); 144 ret = false; 145 } else { 146 test->ct_ops->lo_unlock(mutex); 147 } 148 149 return (ret); 150 } 151 152 static bool 153 clock_test_mutex_inv_to_abs(const clock_test_t *test, void *prim) 154 { 155 bool ret = true; 156 pthread_mutex_t *mutex = prim; 157 int p; 158 159 if ((p = pthread_mutex_timedlock(mutex, &clock_to_invns)) != EINVAL) { 160 warnx("TEST FAILED: %s: pthread_mutex_timedlock with invalid " 161 "timeout returned %s, not EINVAL", test->ct_desc, 162 strerrorname_np(p)); 163 ret = false; 164 } 165 166 if ((p = pthread_mutex_clocklock(mutex, CLOCK_MONOTONIC, 167 &clock_to_invns)) != EINVAL) { 168 warnx("TEST FAILED: %s: pthread_mutex_clocklock with invalid " 169 "timeout returned %s, not EINVAL", test->ct_desc, 170 strerrorname_np(p)); 171 ret = false; 172 } 173 174 return (ret); 175 } 176 177 static bool 178 clock_test_mutex_inv_to_ign_rel(const clock_test_t *test, void *prim) 179 { 180 bool ret = true; 181 pthread_mutex_t *mutex = prim; 182 const struct timespec *specs[] = { &clock_to_invns, &clock_to_invnegs, 183 &clock_to_invnegns }; 184 const char *descs[] = { "too many nanoseconds", "negative seconds", 185 "negative nanoseconds" }; 186 int p; 187 188 for (size_t i = 0; i < ARRAY_SIZE(specs); i++) { 189 if ((p = pthread_mutex_reltimedlock_np(mutex, specs[i])) != 0) { 190 warnx("TEST FAILED: %s: pthread_mutex_reltimedlock_np " 191 "failed with invalid timeout %s when the lock when " 192 "lock was available: expected success, found %s", 193 test->ct_desc, descs[i], strerrorname_np(p)); 194 ret = false; 195 } else { 196 test->ct_ops->lo_unlock(mutex); 197 } 198 199 if ((p = pthread_mutex_relclocklock_np(mutex, CLOCK_MONOTONIC, 200 specs[i])) != 0) { 201 warnx("TEST FAILED: %s: pthread_mutex_relclocklock_np " 202 "failed with invalid timeout %s when the lock when " 203 "lock was available: expected success, found %s", 204 test->ct_desc, descs[i], strerrorname_np(p)); 205 ret = false; 206 } else { 207 test->ct_ops->lo_unlock(mutex); 208 } 209 } 210 211 return (ret); 212 } 213 214 static bool 215 clock_test_mutex_inv_to_rel(const clock_test_t *test, void *prim) 216 { 217 bool ret = true; 218 pthread_mutex_t *mutex = prim; 219 const struct timespec *specs[] = { &clock_to_invns, &clock_to_invnegs, 220 &clock_to_invnegns }; 221 const char *descs[] = { "too many nanoseconds", "negative seconds", 222 "negative nanoseconds" }; 223 int p; 224 225 for (size_t i = 0; i < ARRAY_SIZE(specs); i++) { 226 if ((p = pthread_mutex_reltimedlock_np(mutex, specs[i])) != 227 EINVAL) { 228 warnx("TEST FAILED: %s: pthread_mutex_reltimedlock_np " 229 "with invalid timeout %s returned %s, not EINVAL", 230 test->ct_desc, descs[i], strerrorname_np(p)); 231 ret = false; 232 } 233 234 if ((p = pthread_mutex_relclocklock_np(mutex, CLOCK_MONOTONIC, 235 specs[i])) != EINVAL) { 236 warnx("TEST FAILED: %s: pthread_mutex_relclocklock_np " 237 "with invalid timeout %s returned %s, not EINVAL", 238 test->ct_desc, descs[i], strerrorname_np(p)); 239 ret = false; 240 } 241 } 242 243 return (ret); 244 } 245 246 static bool 247 clock_test_mutex_to_abs(const clock_test_t *test, void *prim) 248 { 249 pthread_mutex_t *mutex = prim; 250 struct timespec to; 251 int p; 252 bool ret = true, elapse; 253 254 clock_rel_to_abs(CLOCK_REALTIME, &clock_to_100ms, &to); 255 p = pthread_mutex_timedlock(mutex, &to); 256 elapse = clock_abs_after(CLOCK_REALTIME, &to); 257 if (p != ETIMEDOUT) { 258 warnx("TEST FAILED: %s pthread_mutex_timedlock on locked mutex " 259 "returned %s, not ETIMEDOUT", test->ct_desc, 260 strerrorname_np(p)); 261 ret = false; 262 } 263 if (!elapse) { 264 warnx("TEST FAILED: %s: pthread_mutex_timedlock on locked " 265 "mutex did not block long enough!", test->ct_desc); 266 ret = false; 267 } 268 269 clock_rel_to_abs(CLOCK_REALTIME, &clock_to_100ms, &to); 270 p = pthread_mutex_clocklock(mutex, CLOCK_REALTIME, &to); 271 elapse = clock_abs_after(CLOCK_REALTIME, &to); 272 if (p != ETIMEDOUT) { 273 warnx("TEST FAILED: %s: pthread_mutex_clocklock on locked " 274 "mutex with CLOCK_REALTIME returned %s, not ETIMEDOUT", 275 test->ct_desc, strerrorname_np(p)); 276 ret = false; 277 } 278 if (!elapse) { 279 warnx("TEST FAILED: %s: pthread_mutex_clocklock on locked " 280 "mutex with CLOCK_REALTIME did not block long enough!", 281 test->ct_desc); 282 ret = false; 283 } 284 285 clock_rel_to_abs(CLOCK_HIGHRES, &clock_to_100ms, &to); 286 p = pthread_mutex_clocklock(mutex, CLOCK_HIGHRES, &to); 287 elapse = clock_abs_after(CLOCK_HIGHRES, &to); 288 if (p != ETIMEDOUT) { 289 warnx("TEST FAILED: %s: pthread_mutex_clocklock on locked " 290 "mutex with CLOCK_HIGHRES returned %s, not ETIMEDOUT", 291 test->ct_desc, strerrorname_np(p)); 292 ret = false; 293 } 294 if (!elapse) { 295 warnx("TEST FAILED: %s: pthread_mutex_clocklock on locked " 296 "mutex with CLOCK_HIGHRES did not block long enough!", 297 test->ct_desc); 298 ret = false; 299 } 300 301 return (ret); 302 } 303 304 static bool 305 clock_test_mutex_to_rel(const clock_test_t *test, void *prim) 306 { 307 pthread_mutex_t *mutex = prim; 308 struct timespec start; 309 int p; 310 bool ret = true, elapse; 311 312 if (clock_gettime(CLOCK_REALTIME, &start) != 0) { 313 err(EXIT_FAILURE, "failed to read clock %d", CLOCK_REALTIME); 314 } 315 p = pthread_mutex_reltimedlock_np(mutex, &clock_to_100ms); 316 elapse = clock_rel_after(CLOCK_REALTIME, &start, &clock_to_100ms); 317 if (p != ETIMEDOUT) { 318 warnx("TEST FAILED: %s: pthread_mutex_reltimedlock_np on " 319 "locked mutex returned %s, not ETIMEDOUT", test->ct_desc, 320 strerrorname_np(p)); 321 ret = false; 322 } 323 if (!elapse) { 324 warnx("TEST FAILED: %s: pthread_mutex_reltimedlock_np on " 325 "locked mutex did not block long enough!", test->ct_desc); 326 ret = false; 327 } 328 329 if (clock_gettime(CLOCK_REALTIME, &start) != 0) { 330 err(EXIT_FAILURE, "failed to read clock %d", CLOCK_REALTIME); 331 } 332 p = pthread_mutex_relclocklock_np(mutex, CLOCK_REALTIME, 333 &clock_to_100ms); 334 elapse = clock_rel_after(CLOCK_REALTIME, &start, &clock_to_100ms); 335 if (p != ETIMEDOUT) { 336 warnx("TEST FAILED: %s: pthread_mutex_relclocklock_np on " 337 "locked mutex with CLOCK_REALTIME returned %s, not " 338 "ETIMEDOUT", test->ct_desc, strerrorname_np(p)); 339 ret = false; 340 } 341 if (!elapse) { 342 warnx("TEST FAILED: %s: pthread_mutex_relclocklock_np on " 343 "locked mutex with CLOCK_REALTIME did not block long " 344 "enough!", test->ct_desc); 345 ret = false; 346 } 347 348 if (clock_gettime(CLOCK_HIGHRES, &start) != 0) { 349 err(EXIT_FAILURE, "failed to read clock %d", CLOCK_HIGHRES); 350 } 351 p = pthread_mutex_relclocklock_np(mutex, CLOCK_HIGHRES, 352 &clock_to_100ms); 353 elapse = clock_rel_after(CLOCK_HIGHRES, &start, &clock_to_100ms); 354 if (p != ETIMEDOUT) { 355 warnx("TEST FAILED: %s: pthread_mutex_relclocklock_np on " 356 "locked mutex with CLOCK_HIGHRES returned %s, not " 357 "ETIMEDOUT", test->ct_desc, strerrorname_np(p)); 358 ret = false; 359 } 360 if (!elapse) { 361 warnx("TEST FAILED: %s: pthread_mutex_relclocklock_np on " 362 "locked mutex with CLOCK_HIGHRES did not block long " 363 "enough!", test->ct_desc); 364 ret = false; 365 } 366 367 return (ret); 368 } 369 370 const clock_test_t clock_mutex_tests[] = { { 371 .ct_desc = "mutex: invalid and unsupported clock sources", 372 .ct_ops = &clock_lock_mutex_ops, 373 .ct_test = clock_test_mutex_invalid_source 374 }, { 375 .ct_desc = "mutex: invalid timeout works if lock available (absolute)", 376 .ct_ops = &clock_lock_mutex_ops, 377 .ct_test = clock_test_mutex_inv_to_ign_abs 378 }, { 379 .ct_desc = "mutex: invalid timeout works if lock available (relative)", 380 .ct_ops = &clock_lock_mutex_ops, 381 .ct_test = clock_test_mutex_inv_to_ign_rel 382 }, { 383 .ct_desc = "mutex: invalid timeout fails if lock taken (absolute)", 384 .ct_ops = &clock_lock_mutex_ops, 385 .ct_test = clock_test_mutex_inv_to_abs, 386 .ct_enter = true 387 }, { 388 .ct_desc = "mutex: invalid timeout fails if lock taken (relative)", 389 .ct_ops = &clock_lock_mutex_ops, 390 .ct_test = clock_test_mutex_inv_to_rel, 391 .ct_enter = true 392 }, { 393 .ct_desc = "mutex: timeout fires correctly (absolute)", 394 .ct_ops = &clock_lock_mutex_ops, 395 .ct_test = clock_test_mutex_to_abs, 396 .ct_enter = true 397 }, { 398 .ct_desc = "mutex: timeout fires correctly (relative)", 399 .ct_ops = &clock_lock_mutex_ops, 400 .ct_test = clock_test_mutex_to_rel, 401 .ct_enter = true 402 } }; 403 404 size_t clock_mutex_ntests = ARRAY_SIZE(clock_mutex_tests); 405