xref: /illumos-gate/usr/src/test/libc-tests/tests/mutex-16200.c (revision 8b0687e22a8de32264ac01083e07099701686911)
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_flags_t;
42 
43 typedef struct {
44 	const char *mt_desc;
45 	mutex_test_flags_t mt_flags;
46 	int mt_type;
47 	int mt_ret;
48 } mutex_test_t;
49 
50 const mutex_test_t mutex_tests[] = {
51 	{
52 		.mt_desc = "pthread attr NULL",
53 		.mt_flags = MUTEX_TEST_F_DEADLOCK,
54 		.mt_ret = INT32_MIN
55 	}, {
56 		.mt_desc = "pthread attr unset",
57 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_DEADLOCK,
58 		.mt_ret = INT32_MIN
59 	}, {
60 		.mt_desc = "pthrad attr default",
61 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
62 		    MUTEX_TEST_F_DEADLOCK,
63 		.mt_type = PTHREAD_MUTEX_DEFAULT,
64 		.mt_ret = INT32_MIN
65 	}, {
66 		.mt_desc = "pthread attr normal",
67 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
68 		    MUTEX_TEST_F_DEADLOCK,
69 		.mt_type = PTHREAD_MUTEX_NORMAL,
70 		/* Set to a value that we should never see or get to */
71 		.mt_ret = INT32_MIN
72 	}, {
73 		.mt_desc = "pthread attr recursive",
74 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE,
75 		.mt_type = PTHREAD_MUTEX_RECURSIVE,
76 		.mt_ret = 0
77 	}, {
78 		.mt_desc = "pthread attr errorcheck",
79 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE,
80 		.mt_type = PTHREAD_MUTEX_ERRORCHECK,
81 		.mt_ret = EDEADLK
82 	}, {
83 		.mt_desc = "illumos USYNC_THREAD",
84 		.mt_flags = MUTEX_TEST_F_DEADLOCK | MUTEX_TEST_F_ILLUMOS,
85 		.mt_type = USYNC_THREAD,
86 		.mt_ret = INT32_MAX
87 	}, {
88 		.mt_desc = "illumos error check",
89 		.mt_flags = MUTEX_TEST_F_ILLUMOS,
90 		.mt_type = USYNC_THREAD | LOCK_ERRORCHECK,
91 		.mt_ret = EDEADLK
92 	}, {
93 		.mt_desc = "illumos recursive",
94 		.mt_flags = MUTEX_TEST_F_ILLUMOS,
95 		.mt_type = USYNC_THREAD | LOCK_RECURSIVE,
96 		.mt_ret = 0
97 	}, {
98 		.mt_desc = "illumos recursive error check",
99 		.mt_flags = MUTEX_TEST_F_ILLUMOS,
100 		.mt_type = USYNC_THREAD | LOCK_RECURSIVE | LOCK_ERRORCHECK,
101 		.mt_ret = 0
102 	}
103 };
104 
105 static void *
106 mutex_test_thr(void *arg)
107 {
108 	int ret;
109 	pthread_mutexattr_t attr, *attrp = NULL;
110 	const mutex_test_t *test = arg;
111 
112 	if ((test->mt_flags & MUTEX_TEST_F_USE_ATTR) != 0) {
113 		VERIFY0(pthread_mutexattr_init(&attr));
114 		attrp = &attr;
115 
116 		if ((test->mt_flags & MUTEX_TEST_F_SET_TYPE) != 0) {
117 			VERIFY0(pthread_mutexattr_settype(&attr,
118 			    test->mt_type));
119 		}
120 	}
121 
122 	if ((test->mt_flags & MUTEX_TEST_F_ILLUMOS) != 0) {
123 		mutex_t m;
124 
125 		VERIFY0(mutex_init(&m, test->mt_type, NULL));
126 		VERIFY0(mutex_lock(&m));
127 		ret = mutex_lock(&m);
128 	} else {
129 		pthread_mutex_t pm;
130 
131 		VERIFY0(pthread_mutex_init(&pm, attrp));
132 		VERIFY0(pthread_mutex_lock(&pm));
133 		ret = pthread_mutex_lock(&pm);
134 	}
135 
136 	return ((void *)(uintptr_t)ret);
137 }
138 
139 
140 /*
141  * Attempt to determine if a thread is still going and we should wait, if it has
142  * potentially terminated, or if it is blocked in lwp_park() suggesting it has
143  * been deadlocked.
144  */
145 typedef enum {
146 	THR_STATE_PARKED,
147 	THR_STATE_DEAD,
148 	THR_STATE_RUNNING
149 } thr_state_t;
150 
151 static thr_state_t
152 mutex_test_thr_state(thread_t thr)
153 {
154 	lwpstatus_t lwp;
155 	char name[SYS2STR_MAX];
156 
157 	if (proc_get_lwpstatus(getpid(), (uint_t)thr, &lwp) != 0) {
158 		int e = errno;
159 		switch (e) {
160 		case ENOENT:
161 			return (THR_STATE_DEAD);
162 		default:
163 			errc(EXIT_FAILURE, e, "fatal error: got unexpected "
164 			    "error while trying to get lwpstatus");
165 		}
166 	}
167 
168 	if ((lwp.pr_flags & PR_ASLEEP) == 0) {
169 		return (THR_STATE_RUNNING);
170 	}
171 
172 	if (proc_sysname(lwp.pr_syscall, name, sizeof (name)) == 0) {
173 		return (THR_STATE_RUNNING);
174 	}
175 
176 	if (strcmp(name, "lwp_park") == 0) {
177 		return (THR_STATE_PARKED);
178 	}
179 
180 	return (THR_STATE_RUNNING);
181 }
182 
183 static bool
184 mutex_test_run_one(const mutex_test_t *test)
185 {
186 	int err, lock;
187 	thread_t thr;
188 	thr_state_t state;
189 	void *val;
190 
191 	err = thr_create(NULL, 0, mutex_test_thr, (void *)test, 0, &thr);
192 	if (err != 0) {
193 		errc(EXIT_FAILURE, err, "fatal test error: could not create "
194 		    "thread for %s", test->mt_desc);
195 	}
196 
197 	/*
198 	 * Wait for the thread to deadlock or exit and then continue.
199 	 */
200 	while ((state = mutex_test_thr_state(thr)) == THR_STATE_RUNNING) {
201 		struct timespec sleep;
202 
203 		sleep.tv_sec = 0;
204 		sleep.tv_nsec = MSEC2NSEC(10);
205 		(void) nanosleep(&sleep, NULL);
206 	}
207 
208 	if (state == THR_STATE_PARKED) {
209 		if ((test->mt_flags & MUTEX_TEST_F_DEADLOCK) != 0) {
210 			(void) printf("TEST PASSED: %s: successfully "
211 			    "deadlocked\n", test->mt_desc);
212 			return (true);
213 		}
214 
215 		(void) sleep(100000);
216 
217 		warnx("TEST FAILED: %s: thread deadlocked, but expected return "
218 		    "value %d", test->mt_desc, test->mt_ret);
219 		return (false);
220 	}
221 
222 	VERIFY0(thr_join(thr, NULL, &val));
223 	lock = (int)(uintptr_t)val;
224 	if ((test->mt_flags & MUTEX_TEST_F_DEADLOCK) != 0) {
225 		warnx("TEST FAILED: %s: expected deadlock, but mutex lock "
226 		    "returned %d", test->mt_desc, lock);
227 		return (false);
228 	} else if (lock != test->mt_ret) {
229 		warnx("TEST FAILED: %s: found return value %d, expected %d",
230 		    test->mt_desc, lock, test->mt_ret);
231 		return (false);
232 	} else {
233 		(void) printf("TEST PASSED: %s: got correct lock return value "
234 		    "(%d)\n", test->mt_desc, test->mt_ret);
235 		return (true);
236 	}
237 }
238 
239 int
240 main(void)
241 {
242 	int ret = EXIT_SUCCESS;
243 
244 	if (getenv("_THREAD_ASYNC_SAFE") != NULL) {
245 		errx(EXIT_FAILURE, "cannot run tests because "
246 		    "_THREAD_ASYNC_SAFE is set in the environment!");
247 	}
248 
249 	for (size_t i = 0; i < ARRAY_SIZE(mutex_tests); i++) {
250 		if (!mutex_test_run_one(&mutex_tests[i])) {
251 			ret = EXIT_FAILURE;
252 		}
253 	}
254 
255 	/*
256 	 * Ensure any lingering threads don't keep us around.
257 	 */
258 	exit(ret);
259 }
260