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
clock_cond_create(const char * desc,void ** argp)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
clock_cond_destroy(void * arg)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
clock_cond_static_create(const char * desc,void ** argp)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
clock_test_cond_invalid_source(const clock_test_t * test,void * arg)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
clock_test_cond_inv_to_abs(const clock_test_t * test,void * prim)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
clock_test_cond_inv_to_rel(const clock_test_t * test,void * prim)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
clock_test_cond_to_abs(const clock_test_t * test,void * prim)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
clock_test_cond_to_rel(const clock_test_t * test,void * prim)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
clock_test_cond_eperm(const clock_test_t * test,void * prim)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