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 * conditionn variable-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 pthread_cond_t clock_cond_static = PTHREAD_COND_INITIALIZER; 32 static pthread_mutex_t clock_mutex_static = 33 PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; 34 35 typedef struct { 36 pthread_mutex_t cc_mutex; 37 pthread_cond_t cc_cond; 38 } clock_cond_t; 39 40 static void 41 clock_cond_create(const char *desc, void **argp) 42 { 43 int ret; 44 clock_cond_t *cc; 45 pthread_mutexattr_t attr; 46 47 cc = calloc(1, sizeof (clock_cond_t)); 48 if (cc == NULL) { 49 err(EXIT_FAILURE, "TEST FAILED: %s: failed to allocate memory " 50 "for a mutex and condition variable", desc); 51 } 52 53 if ((ret = pthread_cond_init(&cc->cc_cond, NULL)) != 0) { 54 errc(EXIT_FAILURE, ret, "TEST FAILED: %s: failed to create " 55 "condition variable", desc); 56 } 57 58 if ((ret = pthread_mutexattr_init(&attr)) != 0) { 59 errc(EXIT_FAILURE, ret, "TEST FAILED: %s: failed to initialize " 60 "mutex attributes", desc); 61 } 62 63 if ((ret = pthread_mutexattr_settype(&attr, 64 PTHREAD_MUTEX_ERRORCHECK)) != 0) { 65 errc(EXIT_FAILURE, ret, "TEST FAILED: %s: failed to set mutex " 66 "type to error checking", desc); 67 } 68 69 if ((ret = pthread_mutex_init(&cc->cc_mutex, &attr)) != 0) { 70 errc(EXIT_FAILURE, ret, "TEST FAILED: %s: failed to create " 71 "mutex", desc); 72 } 73 74 *argp = cc; 75 } 76 77 static void 78 clock_cond_destroy(void *arg) 79 { 80 clock_cond_t *cc = arg; 81 82 VERIFY0(pthread_mutex_destroy(&cc->cc_mutex)); 83 VERIFY0(pthread_cond_destroy(&cc->cc_cond)); 84 free(cc); 85 } 86 87 /* 88 * Unlike the other primitives, there is no notion of a condition variable being 89 * locked or unlocked. Hence there is no implementation. 90 */ 91 const lock_ops_t clock_lock_cond_ops = { 92 .lo_create = clock_cond_create, 93 .lo_destroy = clock_cond_destroy 94 }; 95 96 static void 97 clock_cond_static_create(const char *desc, void **argp) 98 { 99 clock_cond_t *cc; 100 101 cc = calloc(1, sizeof (clock_cond_t)); 102 if (cc == NULL) { 103 err(EXIT_FAILURE, "TEST FAILED: %s: failed to allocate memory " 104 "for a mutex and condition variable", desc); 105 } 106 107 (void) memcpy(&cc->cc_mutex, &clock_mutex_static, 108 sizeof (pthread_mutex_t)); 109 (void) memcpy(&cc->cc_cond, &clock_cond_static, 110 sizeof (pthread_cond_t)); 111 *argp = cc; 112 } 113 114 /* 115 * This uses the static versions that we have created of each primitve. This is 116 * a regression test for #16683. 117 */ 118 const lock_ops_t clock_lock_cond_static_ops = { 119 .lo_create = clock_cond_static_create, 120 .lo_destroy = clock_cond_destroy 121 }; 122 123 static bool 124 clock_test_cond_invalid_source(const clock_test_t *test, void *arg) 125 { 126 bool ret = true; 127 clock_cond_t *cc = arg; 128 pthread_mutex_t *mutex = &cc->cc_mutex; 129 pthread_cond_t *cond = &cc->cc_cond; 130 const clockid_t clocks[] = { 0x7777, INT32_MAX, 0x23, CLOCK_VIRTUAL, 131 CLOCK_THREAD_CPUTIME_ID, CLOCK_PROCESS_CPUTIME_ID }; 132 int p; 133 134 pthread_mutex_enter_np(mutex); 135 for (size_t i = 0; i < ARRAY_SIZE(clocks); i++) { 136 clockid_t c = clocks[i]; 137 138 if ((p = pthread_cond_clockwait(cond, mutex, c, 139 &clock_to_100ms)) != EINVAL) { 140 warnx("TEST FAILED: %s: pthread_cond_clockwait with " 141 "clock 0x%x returned %s, not EINVAL", test->ct_desc, 142 c, strerrorname_np(p)); 143 ret = false; 144 } 145 146 if ((p = pthread_cond_relclockwait_np(cond, mutex, c, 147 &clock_to_100ms)) != EINVAL) { 148 warnx("TEST FAILED: %s: pthread_cond_relclockwait_np " 149 "with clock 0x%x returned %s, not EINVAL", 150 test->ct_desc, c, strerrorname_np(p)); 151 ret = false; 152 } 153 } 154 pthread_mutex_exit_np(mutex); 155 156 return (ret); 157 } 158 159 static bool 160 clock_test_cond_inv_to_abs(const clock_test_t *test, void *prim) 161 { 162 bool ret = true; 163 clock_cond_t *cc = prim; 164 int p; 165 166 pthread_mutex_enter_np(&cc->cc_mutex); 167 if ((p = pthread_cond_timedwait(&cc->cc_cond, &cc->cc_mutex, 168 &clock_to_invns)) != EINVAL) { 169 warnx("TEST FAILED: %s: pthread_cond_timedwait with invalid " 170 "timeout returned %s, not EINVAL", test->ct_desc, 171 strerrorname_np(p)); 172 ret = false; 173 } 174 175 if ((p = pthread_cond_clockwait(&cc->cc_cond, &cc->cc_mutex, 176 CLOCK_MONOTONIC, &clock_to_invns)) != EINVAL) { 177 warnx("TEST FAILED: %s: pthread_cond_clockwait with invalid " 178 "timeout returned %s, not EINVAL", test->ct_desc, 179 strerrorname_np(p)); 180 ret = false; 181 } 182 pthread_mutex_exit_np(&cc->cc_mutex); 183 184 return (ret); 185 } 186 187 static bool 188 clock_test_cond_inv_to_rel(const clock_test_t *test, void *prim) 189 { 190 bool ret = true; 191 clock_cond_t *cc = prim; 192 const struct timespec *specs[] = { &clock_to_invns, &clock_to_invnegs, 193 &clock_to_invnegns }; 194 const char *descs[] = { "too many nanoseconds", "negative seconds", 195 "negative nanoseconds" }; 196 int p; 197 198 pthread_mutex_enter_np(&cc->cc_mutex); 199 for (size_t i = 0; i < ARRAY_SIZE(specs); i++) { 200 if ((p = pthread_cond_reltimedwait_np(&cc->cc_cond, 201 &cc->cc_mutex, specs[i])) != EINVAL) { 202 warnx("TEST FAILED: %s: pthread_cond_reltimedwait_np " 203 "with invalid timeout %s returned %s, not EINVAL", 204 test->ct_desc, descs[i], strerrorname_np(p)); 205 ret = false; 206 } 207 208 if ((p = pthread_cond_relclockwait_np(&cc->cc_cond, 209 &cc->cc_mutex, CLOCK_MONOTONIC, specs[i])) != EINVAL) { 210 warnx("TEST FAILED: %s: pthread_cond_relclockwait_np " 211 "with invalid timeout %s returned %s, not EINVAL", 212 test->ct_desc, descs[i], strerrorname_np(p)); 213 ret = false; 214 } 215 } 216 pthread_mutex_exit_np(&cc->cc_mutex); 217 218 return (ret); 219 } 220 221 static bool 222 clock_test_cond_to_abs(const clock_test_t *test, void *prim) 223 { 224 clock_cond_t *cc = prim; 225 struct timespec to; 226 int p; 227 bool ret = true, elapse; 228 229 pthread_mutex_enter_np(&cc->cc_mutex); 230 clock_rel_to_abs(CLOCK_REALTIME, &clock_to_100ms, &to); 231 p = pthread_cond_timedwait(&cc->cc_cond, &cc->cc_mutex, &to); 232 elapse = clock_abs_after(CLOCK_REALTIME, &to); 233 if (p != ETIMEDOUT) { 234 warnx("TEST FAILED: %s pthread_cond_timedwait returned %s, not " 235 "ETIMEDOUT", test->ct_desc, strerrorname_np(p)); 236 ret = false; 237 } 238 if (!elapse) { 239 warnx("TEST FAILED: %s: pthread_cond_timedwait did not block " 240 "long enough!", test->ct_desc); 241 ret = false; 242 } 243 244 clock_rel_to_abs(CLOCK_REALTIME, &clock_to_100ms, &to); 245 p = pthread_cond_clockwait(&cc->cc_cond, &cc->cc_mutex, CLOCK_REALTIME, 246 &to); 247 elapse = clock_abs_after(CLOCK_REALTIME, &to); 248 if (p != ETIMEDOUT) { 249 warnx("TEST FAILED: %s: pthread_cond_clockwait with " 250 "CLOCK_REALTIME returned %s, not ETIMEDOUT", test->ct_desc, 251 strerrorname_np(p)); 252 ret = false; 253 } 254 if (!elapse) { 255 warnx("TEST FAILED: %s: pthread_cond_clockwait with " 256 "CLOCK_REALTIME did not block long enough!", test->ct_desc); 257 ret = false; 258 } 259 260 clock_rel_to_abs(CLOCK_HIGHRES, &clock_to_100ms, &to); 261 p = pthread_cond_clockwait(&cc->cc_cond, &cc->cc_mutex, CLOCK_HIGHRES, 262 &to); 263 elapse = clock_abs_after(CLOCK_HIGHRES, &to); 264 if (p != ETIMEDOUT) { 265 warnx("TEST FAILED: %s: pthread_cond_clockwait with " 266 "CLOCK_HIGHRES returned %s, not ETIMEDOUT", test->ct_desc, 267 strerrorname_np(p)); 268 ret = false; 269 } 270 if (!elapse) { 271 warnx("TEST FAILED: %s: pthread_cond_clockwait with " 272 "CLOCK_HIGHRES did not block long enough!", test->ct_desc); 273 ret = false; 274 } 275 pthread_mutex_exit_np(&cc->cc_mutex); 276 277 return (ret); 278 } 279 280 static bool 281 clock_test_cond_to_rel(const clock_test_t *test, void *prim) 282 { 283 clock_cond_t *cc = prim; 284 struct timespec start; 285 int p; 286 bool ret = true, elapse; 287 288 pthread_mutex_enter_np(&cc->cc_mutex); 289 if (clock_gettime(CLOCK_REALTIME, &start) != 0) { 290 err(EXIT_FAILURE, "failed to read clock %d", CLOCK_REALTIME); 291 } 292 p = pthread_cond_reltimedwait_np(&cc->cc_cond, &cc->cc_mutex, 293 &clock_to_100ms); 294 elapse = clock_rel_after(CLOCK_REALTIME, &start, &clock_to_100ms); 295 if (p != ETIMEDOUT) { 296 warnx("TEST FAILED: %s: pthread_cond_reltimedwait_np returned " 297 "%s, not ETIMEDOUT", test->ct_desc, 298 strerrorname_np(p)); 299 ret = false; 300 } 301 if (!elapse) { 302 warnx("TEST FAILED: %s: pthread_cond_reltimedwait_np did not " 303 "block long enough!", test->ct_desc); 304 ret = false; 305 } 306 307 if (clock_gettime(CLOCK_REALTIME, &start) != 0) { 308 err(EXIT_FAILURE, "failed to read clock %d", CLOCK_REALTIME); 309 } 310 p = pthread_cond_relclockwait_np(&cc->cc_cond, &cc->cc_mutex, 311 CLOCK_REALTIME, &clock_to_100ms); 312 elapse = clock_rel_after(CLOCK_REALTIME, &start, &clock_to_100ms); 313 if (p != ETIMEDOUT) { 314 warnx("TEST FAILED: %s: pthread_cond_relclockwait_np with " 315 "CLOCK_REALTIME returned %s, not ETIMEDOUT", test->ct_desc, 316 strerrorname_np(p)); 317 ret = false; 318 } 319 if (!elapse) { 320 warnx("TEST FAILED: %s: pthread_cond_relclockwait_np with " 321 "CLOCK_REALTIME did not block long enough!", test->ct_desc); 322 ret = false; 323 } 324 325 if (clock_gettime(CLOCK_HIGHRES, &start) != 0) { 326 err(EXIT_FAILURE, "failed to read clock %d", CLOCK_HIGHRES); 327 } 328 p = pthread_cond_relclockwait_np(&cc->cc_cond, &cc->cc_mutex, 329 CLOCK_HIGHRES, &clock_to_100ms); 330 elapse = clock_rel_after(CLOCK_HIGHRES, &start, &clock_to_100ms); 331 if (p != ETIMEDOUT) { 332 warnx("TEST FAILED: %s: pthread_cond_relclockwait_np with " 333 "CLOCK_HIGHRES returned %s, not ETIMEDOUT", test->ct_desc, 334 strerrorname_np(p)); 335 ret = false; 336 } 337 if (!elapse) { 338 warnx("TEST FAILED: %s: pthread_cond_relclockwait_np with " 339 "CLOCK_HIGHRES did not block long enough!", test->ct_desc); 340 ret = false; 341 } 342 pthread_mutex_exit_np(&cc->cc_mutex); 343 344 return (ret); 345 } 346 347 static bool 348 clock_test_cond_eperm(const clock_test_t *test, void *prim) 349 { 350 bool ret = true; 351 clock_cond_t *cc = prim; 352 int p; 353 354 if ((p = pthread_cond_timedwait(&cc->cc_cond, &cc->cc_mutex, 355 &clock_to_100ms)) != EPERM) { 356 warnx("TEST FAILED: %s: pthread_cond_timedwait without held " 357 "mutex returned %s, not EPERM", test->ct_desc, 358 strerrorname_np(p)); 359 ret = false; 360 } 361 362 if ((p = pthread_cond_clockwait(&cc->cc_cond, &cc->cc_mutex, 363 CLOCK_MONOTONIC, &clock_to_100ms)) != EPERM) { 364 warnx("TEST FAILED: %s: pthread_cond_clockwait without held " 365 "mutex returned %s, not EPERM", test->ct_desc, 366 strerrorname_np(p)); 367 ret = false; 368 } 369 370 if ((p = pthread_cond_reltimedwait_np(&cc->cc_cond, &cc->cc_mutex, 371 &clock_to_100ms)) != EPERM) { 372 warnx("TEST FAILED: %s: pthread_cond_reltimedwait_np without " 373 "held mutex returned %s, not EPERM", test->ct_desc, 374 strerrorname_np(p)); 375 ret = false; 376 } 377 378 if ((p = pthread_cond_relclockwait_np(&cc->cc_cond, &cc->cc_mutex, 379 CLOCK_HIGHRES, &clock_to_100ms)) != EPERM) { 380 warnx("TEST FAILED: %s: pthread_cond_relclockwait_np without " 381 "held mutex returned %s, not EPERM", test->ct_desc, 382 strerrorname_np(p)); 383 ret = false; 384 } 385 386 return (ret); 387 } 388 389 const clock_test_t clock_cond_tests[] = { { 390 .ct_desc = "cond: invalid and unsupported clock sources", 391 .ct_ops = &clock_lock_cond_ops, 392 .ct_test = clock_test_cond_invalid_source 393 }, { 394 .ct_desc = "cond: invalid timeout fails (absolute)", 395 .ct_ops = &clock_lock_cond_ops, 396 .ct_test = clock_test_cond_inv_to_abs 397 }, { 398 .ct_desc = "cond: invalid timeout fails (relative)", 399 .ct_ops = &clock_lock_cond_ops, 400 .ct_test = clock_test_cond_inv_to_rel 401 }, { 402 .ct_desc = "cond: timeout fires correctly (absolute)", 403 .ct_ops = &clock_lock_cond_ops, 404 .ct_test = clock_test_cond_to_abs 405 }, { 406 .ct_desc = "cond: timeout fires correctly (relative)", 407 .ct_ops = &clock_lock_cond_ops, 408 .ct_test = clock_test_cond_to_rel 409 }, { 410 .ct_desc = "cond: fails without lock", 411 .ct_ops = &clock_lock_cond_ops, 412 .ct_test = clock_test_cond_eperm 413 }, { 414 .ct_desc = "cond: invalid and unsupported clock sources (static)", 415 .ct_ops = &clock_lock_cond_static_ops, 416 .ct_test = clock_test_cond_invalid_source 417 }, { 418 .ct_desc = "cond: invalid timeout fails (absolute, static)", 419 .ct_ops = &clock_lock_cond_static_ops, 420 .ct_test = clock_test_cond_inv_to_abs 421 }, { 422 .ct_desc = "cond: invalid timeout fails (relative, static)", 423 .ct_ops = &clock_lock_cond_static_ops, 424 .ct_test = clock_test_cond_inv_to_rel 425 }, { 426 .ct_desc = "cond: timeout fires correctly (absolute, static)", 427 .ct_ops = &clock_lock_cond_static_ops, 428 .ct_test = clock_test_cond_to_abs 429 }, { 430 .ct_desc = "cond: timeout fires correctly (relative, static)", 431 .ct_ops = &clock_lock_cond_static_ops, 432 .ct_test = clock_test_cond_to_rel 433 }, { 434 .ct_desc = "cond: fails without lock (static)", 435 .ct_ops = &clock_lock_cond_static_ops, 436 .ct_test = clock_test_cond_eperm 437 } }; 438 439 size_t clock_cond_ntests = ARRAY_SIZE(clock_cond_tests); 440