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