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