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 * 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 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 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 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