xref: /illumos-gate/usr/src/test/libc-tests/tests/clocklock/clock_lock_cond.c (revision 7655c6d53c36750b508636f48c73a2de57754e5a)
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