xref: /illumos-gate/usr/src/test/libc-tests/tests/clocklock/clock_lock_mutex.c (revision 90c28423f62864b8d977369710d815fd4227b5f0)
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
clock_mutex_create(const char * desc,void ** argp)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
clock_mutex_destroy(void * arg)64 clock_mutex_destroy(void *arg)
65 {
66 	VERIFY0(pthread_mutex_destroy(arg));
67 	free(arg);
68 }
69 
70 static void
clock_mutex_lock(void * arg)71 clock_mutex_lock(void *arg)
72 {
73 	VERIFY0(pthread_mutex_trylock(arg));
74 }
75 
76 static void
clock_mutex_unlock(void * arg)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
clock_test_mutex_invalid_source(const clock_test_t * test,void * prim)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
clock_test_mutex_inv_to_ign_abs(const clock_test_t * test,void * prim)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
clock_test_mutex_inv_to_abs(const clock_test_t * test,void * prim)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
clock_test_mutex_inv_to_ign_rel(const clock_test_t * test,void * prim)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
clock_test_mutex_inv_to_rel(const clock_test_t * test,void * prim)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
clock_test_mutex_to_abs(const clock_test_t * test,void * prim)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
clock_test_mutex_to_rel(const clock_test_t * test,void * prim)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