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 * Test various mutex types to determine whether we properly deadlock or
18 * generate an error when attempting to take the lock. Note, that the issues
19 * described in 16200 only occur for a single threaded program which this does
20 * not test.
21 */
22
23 #include <err.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <pthread.h>
27 #include <thread.h>
28 #include <synch.h>
29 #include <stdbool.h>
30 #include <sys/sysmacros.h>
31 #include <unistd.h>
32 #include <libproc.h>
33 #include <string.h>
34 #include <sys/debug.h>
35
36 typedef enum {
37 MUTEX_TEST_F_USE_ATTR = 1 << 0,
38 MUTEX_TEST_F_SET_TYPE = 1 << 1,
39 MUTEX_TEST_F_DEADLOCK = 1 << 2,
40 MUTEX_TEST_F_ILLUMOS = 1 << 3,
41 MUTEX_TEST_F_UNLOCK = 1 << 4
42 } mutex_test_flags_t;
43
44 typedef struct {
45 const char *mt_desc;
46 mutex_test_flags_t mt_flags;
47 int mt_type;
48 int mt_ret;
49 } mutex_test_t;
50
51 const mutex_test_t mutex_tests[] = {
52 {
53 .mt_desc = "pthread attr NULL",
54 .mt_flags = MUTEX_TEST_F_DEADLOCK,
55 .mt_ret = INT32_MIN
56 }, {
57 .mt_desc = "pthread attr unset",
58 .mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_DEADLOCK,
59 .mt_ret = INT32_MIN
60 }, {
61 .mt_desc = "pthrad attr default",
62 .mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
63 MUTEX_TEST_F_DEADLOCK,
64 .mt_type = PTHREAD_MUTEX_DEFAULT,
65 .mt_ret = INT32_MIN
66 }, {
67 .mt_desc = "pthread attr normal",
68 .mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
69 MUTEX_TEST_F_DEADLOCK,
70 .mt_type = PTHREAD_MUTEX_NORMAL,
71 /* Set to a value that we should never see or get to */
72 .mt_ret = INT32_MIN
73 }, {
74 .mt_desc = "pthread attr recursive",
75 .mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE,
76 .mt_type = PTHREAD_MUTEX_RECURSIVE,
77 .mt_ret = 0
78 }, {
79 .mt_desc = "pthread attr errorcheck",
80 .mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE,
81 .mt_type = PTHREAD_MUTEX_ERRORCHECK,
82 .mt_ret = EDEADLK
83 }, {
84 .mt_desc = "pthread attr recursive unlock",
85 .mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
86 MUTEX_TEST_F_UNLOCK,
87 .mt_type = PTHREAD_MUTEX_RECURSIVE,
88 .mt_ret = EPERM
89 }, {
90 .mt_desc = "pthread attr errorcheck unlock",
91 .mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
92 MUTEX_TEST_F_UNLOCK,
93 .mt_type = PTHREAD_MUTEX_ERRORCHECK,
94 .mt_ret = EPERM
95 }, {
96 .mt_desc = "illumos USYNC_THREAD",
97 .mt_flags = MUTEX_TEST_F_DEADLOCK | MUTEX_TEST_F_ILLUMOS,
98 .mt_type = USYNC_THREAD,
99 .mt_ret = INT32_MAX
100 }, {
101 .mt_desc = "illumos error check",
102 .mt_flags = MUTEX_TEST_F_ILLUMOS,
103 .mt_type = USYNC_THREAD | LOCK_ERRORCHECK,
104 .mt_ret = EDEADLK
105 }, {
106 .mt_desc = "illumos recursive",
107 .mt_flags = MUTEX_TEST_F_ILLUMOS,
108 .mt_type = USYNC_THREAD | LOCK_RECURSIVE,
109 .mt_ret = 0
110 }, {
111 .mt_desc = "illumos recursive error check",
112 .mt_flags = MUTEX_TEST_F_ILLUMOS,
113 .mt_type = USYNC_THREAD | LOCK_RECURSIVE | LOCK_ERRORCHECK,
114 .mt_ret = 0
115 }, {
116 .mt_desc = "illumos error check unlock",
117 .mt_flags = MUTEX_TEST_F_ILLUMOS | MUTEX_TEST_F_UNLOCK,
118 .mt_type = USYNC_THREAD | LOCK_ERRORCHECK,
119 .mt_ret = EPERM
120 }, {
121 .mt_desc = "illumos recursive error check unlock",
122 .mt_flags = MUTEX_TEST_F_ILLUMOS | MUTEX_TEST_F_UNLOCK,
123 .mt_type = USYNC_THREAD | LOCK_RECURSIVE | LOCK_ERRORCHECK,
124 .mt_ret = EPERM
125 }
126 };
127
128 static void *
mutex_test_thr(void * arg)129 mutex_test_thr(void *arg)
130 {
131 int ret;
132 pthread_mutexattr_t attr, *attrp = NULL;
133 const mutex_test_t *test = arg;
134
135 if ((test->mt_flags & MUTEX_TEST_F_USE_ATTR) != 0) {
136 VERIFY0(pthread_mutexattr_init(&attr));
137 attrp = &attr;
138
139 if ((test->mt_flags & MUTEX_TEST_F_SET_TYPE) != 0) {
140 VERIFY0(pthread_mutexattr_settype(&attr,
141 test->mt_type));
142 }
143 }
144
145 if ((test->mt_flags & MUTEX_TEST_F_UNLOCK) != 0) {
146 if ((test->mt_flags & MUTEX_TEST_F_ILLUMOS) != 0) {
147 mutex_t m;
148
149 VERIFY0(mutex_init(&m, test->mt_type, NULL));
150 ret = mutex_unlock(&m);
151 } else {
152 pthread_mutex_t pm;
153
154 VERIFY0(pthread_mutex_init(&pm, attrp));
155 ret = pthread_mutex_unlock(&pm);
156 }
157
158 return ((void *)(uintptr_t)ret);
159 }
160
161 if ((test->mt_flags & MUTEX_TEST_F_ILLUMOS) != 0) {
162 mutex_t m;
163
164 VERIFY0(mutex_init(&m, test->mt_type, NULL));
165 VERIFY0(mutex_lock(&m));
166 ret = mutex_lock(&m);
167 } else {
168 pthread_mutex_t pm;
169
170 VERIFY0(pthread_mutex_init(&pm, attrp));
171 VERIFY0(pthread_mutex_lock(&pm));
172 ret = pthread_mutex_lock(&pm);
173 }
174
175 return ((void *)(uintptr_t)ret);
176 }
177
178
179 /*
180 * Attempt to determine if a thread is still going and we should wait, if it has
181 * potentially terminated, or if it is blocked in lwp_park() suggesting it has
182 * been deadlocked.
183 */
184 typedef enum {
185 THR_STATE_PARKED,
186 THR_STATE_DEAD,
187 THR_STATE_RUNNING
188 } thr_state_t;
189
190 static thr_state_t
mutex_test_thr_state(thread_t thr)191 mutex_test_thr_state(thread_t thr)
192 {
193 lwpstatus_t lwp;
194 char name[SYS2STR_MAX];
195
196 if (proc_get_lwpstatus(getpid(), (uint_t)thr, &lwp) != 0) {
197 int e = errno;
198 switch (e) {
199 case ENOENT:
200 return (THR_STATE_DEAD);
201 default:
202 errc(EXIT_FAILURE, e, "fatal error: got unexpected "
203 "error while trying to get lwpstatus");
204 }
205 }
206
207 if ((lwp.pr_flags & PR_ASLEEP) == 0) {
208 return (THR_STATE_RUNNING);
209 }
210
211 if (proc_sysname(lwp.pr_syscall, name, sizeof (name)) == 0) {
212 return (THR_STATE_RUNNING);
213 }
214
215 if (strcmp(name, "lwp_park") == 0) {
216 return (THR_STATE_PARKED);
217 }
218
219 return (THR_STATE_RUNNING);
220 }
221
222 static bool
mutex_test_run_one(const mutex_test_t * test)223 mutex_test_run_one(const mutex_test_t *test)
224 {
225 int err, lock;
226 thread_t thr;
227 thr_state_t state;
228 void *val;
229
230 err = thr_create(NULL, 0, mutex_test_thr, (void *)test, 0, &thr);
231 if (err != 0) {
232 errc(EXIT_FAILURE, err, "fatal test error: could not create "
233 "thread for %s", test->mt_desc);
234 }
235
236 /*
237 * Wait for the thread to deadlock or exit and then continue.
238 */
239 while ((state = mutex_test_thr_state(thr)) == THR_STATE_RUNNING) {
240 struct timespec sleep;
241
242 sleep.tv_sec = 0;
243 sleep.tv_nsec = MSEC2NSEC(10);
244 (void) nanosleep(&sleep, NULL);
245 }
246
247 if (state == THR_STATE_PARKED) {
248 if ((test->mt_flags & MUTEX_TEST_F_DEADLOCK) != 0) {
249 (void) printf("TEST PASSED: %s: successfully "
250 "deadlocked\n", test->mt_desc);
251 return (true);
252 }
253
254 (void) sleep(100000);
255
256 warnx("TEST FAILED: %s: thread deadlocked, but expected return "
257 "value %d", test->mt_desc, test->mt_ret);
258 return (false);
259 }
260
261 VERIFY0(thr_join(thr, NULL, &val));
262 lock = (int)(uintptr_t)val;
263 if ((test->mt_flags & MUTEX_TEST_F_DEADLOCK) != 0) {
264 warnx("TEST FAILED: %s: expected deadlock, but mutex lock "
265 "returned %d", test->mt_desc, lock);
266 return (false);
267 } else if (lock != test->mt_ret) {
268 warnx("TEST FAILED: %s: found return value %d, expected %d",
269 test->mt_desc, lock, test->mt_ret);
270 return (false);
271 } else {
272 (void) printf("TEST PASSED: %s: got correct lock return value "
273 "(%d)\n", test->mt_desc, test->mt_ret);
274 return (true);
275 }
276 }
277
278 int
main(void)279 main(void)
280 {
281 int ret = EXIT_SUCCESS;
282
283 if (getenv("_THREAD_ASYNC_SAFE") != NULL) {
284 errx(EXIT_FAILURE, "cannot run tests because "
285 "_THREAD_ASYNC_SAFE is set in the environment!");
286 }
287
288 for (size_t i = 0; i < ARRAY_SIZE(mutex_tests); i++) {
289 if (!mutex_test_run_one(&mutex_tests[i])) {
290 ret = EXIT_FAILURE;
291 }
292 }
293
294 /*
295 * Ensure any lingering threads don't keep us around.
296 */
297 exit(ret);
298 }
299