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 2026 Oxide Computer Company 14 */ 15 16 /* 17 * Regression test for illumos#18362. A libc AIO worker handled only one 18 * SIGAIOCANCEL over its lifetime. A later cancellation left the worker 19 * blocked in read(2). The request did not complete and no notification 20 * was sent. A process could only cancel one request per worker thread. 21 * 22 * The test sets the number of AIO worker threads and drives them through 23 * their states. Counting threads blocked in read(2) confirms the state. 24 * 25 * Set the aio worker pool size via environment variable. 26 * The test loops more times than there are worker threads. 27 * On each iteration: 28 * 1) Get the count of threads blocked in read(2) 29 * 2) Post the read to a pipe. No data is written, so the read blocks. 30 * 3) Loop with timeout until one more thread is blocked in read(2) 31 * 4) Call aio_cancel(3C) 32 * 33 * aio_error() does not report the failure. _aio_cancel_req() sets ECANCELED 34 * before it sends the signal, so polling reports the request cancelled while 35 * the worker is still in read(2). The test checks aio_error() for conformance 36 * only. 37 */ 38 39 #include <sys/types.h> 40 #include <sys/lwp.h> 41 #include <sys/syscall.h> 42 #include <sys/time.h> 43 #include <aio.h> 44 #include <err.h> 45 #include <errno.h> 46 #include <libproc.h> 47 #include <limits.h> 48 #include <procfs.h> 49 #include <pthread.h> 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <string.h> 53 #include <time.h> 54 #include <unistd.h> 55 56 /* 57 * The default worker pool size in aio.c is 4. Rather than duplicate that 58 * private knowledge, this test sets the pool size to 2, a size sufficient 59 * to demonstrate all of the interesting states before and after the fix. 60 */ 61 #define AIO_WORKERS 2 62 63 #define AIO_STR_(x) #x 64 #define AIO_STR(x) AIO_STR_(x) 65 66 /* 67 * Maximum wait for a worker to accept a request, which takes well under a 68 * millisecond. With the defect present, a request queues behind a worker 69 * blocked in read(2) instead of entering read(2) itself. 70 */ 71 #define AIO_SETTLE_MS 1000 72 73 /* 74 * Maximum wait for a notification, which arrives in about a millisecond. A 75 * failing run waits this long for each cancellation that goes unreported. 76 */ 77 #define AIO_DEADLINE_MS 5000 78 static const struct timespec aio_deadline = { 79 AIO_DEADLINE_MS / MILLISEC, 0 80 }; 81 82 /* 83 * The test polls for a worker blocked in read(2) every AIO_POLL_MS before 84 * cancelling the request. AIO_SETTLE_TRIES polls sum to AIO_SETTLE_MS of 85 * sleeping, so the wait is at least that long. 86 */ 87 #define AIO_POLL_MS 100 88 #define AIO_SETTLE_TRIES (AIO_SETTLE_MS / AIO_POLL_MS) 89 static const struct timespec aio_poll = { 90 0, MSEC2NSEC(AIO_POLL_MS) 91 }; 92 93 /* A pool this size makes a failing run take minutes. */ 94 #define AIO_WORKERS_WARN 10 95 96 #define AIO_BUFSZ 64 97 98 static pthread_mutex_t aio_lock = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; 99 static pthread_cond_t aio_cv = PTHREAD_COND_INITIALIZER; 100 static boolean_t *aio_notified; 101 102 static void 103 aio_notify(union sigval sv) 104 { 105 pthread_mutex_enter_np(&aio_lock); 106 aio_notified[sv.sival_int] = B_TRUE; 107 (void) pthread_cond_broadcast(&aio_cv); 108 pthread_mutex_exit_np(&aio_lock); 109 } 110 111 /* 112 * Wait for the notification of request i, up to AIO_DEADLINE_MS. 113 * 114 * One wait is enough. The cancellation we just issued is the only thing that 115 * can complete this request, so either its notification arrives or nothing 116 * ever will. 117 */ 118 static boolean_t 119 aio_await(uint_t i) 120 { 121 boolean_t notified; 122 123 pthread_mutex_enter_np(&aio_lock); 124 if (!aio_notified[i]) { 125 (void) pthread_cond_relclockwait_np(&aio_cv, &aio_lock, 126 CLOCK_MONOTONIC, &aio_deadline); 127 } 128 notified = aio_notified[i]; 129 pthread_mutex_exit_np(&aio_lock); 130 131 return (notified); 132 } 133 134 typedef struct { 135 int air_fd; /* the descriptor our requests read */ 136 uint_t air_readers; /* threads blocked reading it */ 137 } aio_readers_t; 138 139 /* 140 * Count the LWPs blocked in read(2) on our pipe. 141 * 142 * Each such thread is an AIO worker whose request has neither completed nor 143 * been cancelled. 144 * The AIO workers use pread(2) first and then fall back to read(2) on 145 * descriptors that are not seekable, which includes this test's pipe. 146 * 147 * Matching the descriptor keeps the count to threads doing this test's work. 148 * libc does not mark its workers, so an unrelated thread blocked on some 149 * other descriptor would otherwise be indistinguishable from one of them. 150 */ 151 static int 152 aio_count_reader(void *cd, const lwpstatus_t *lsp) 153 { 154 aio_readers_t *air = cd; 155 156 if (lsp->pr_lwpid == (id_t)_lwp_self() || lsp->pr_syscall != SYS_read) 157 return (0); 158 159 if (lsp->pr_nsysarg < 1) { 160 errx(EXIT_FAILURE, "TEST FAILED: read(2) on lwp %ld reports " 161 "%d arguments", (long)lsp->pr_lwpid, lsp->pr_nsysarg); 162 } 163 164 if (lsp->pr_sysarg[0] == air->air_fd) 165 air->air_readers++; 166 167 return (0); 168 } 169 170 static uint_t 171 aio_readers(struct ps_prochandle *P, int fd) 172 { 173 aio_readers_t air = { .air_fd = fd, .air_readers = 0 }; 174 175 if (Plwp_iter(P, aio_count_reader, &air) != 0) { 176 errx(EXIT_FAILURE, "TEST FAILED: could not iterate our own " 177 "threads"); 178 } 179 180 return (air.air_readers); 181 } 182 183 /* 184 * Iterations to run against the worker pool in force. 185 * 186 * A caller may set _AIO_MIN_WORKERS to test other pool sizes. The count 187 * follows the environment rather than AIO_WORKERS. More requests than 188 * workers is what forces a worker to take a second cancellation. 189 * 190 * Inspection of aio.c and testing show that the known failure modes are 191 * fully exercised at two iterations per worker thread plus one. 192 * 193 * A value libc would reject leaves the pool size unknown to us, since libc 194 * substitutes its own default rather than failing. The test stops instead of 195 * copying that default. 196 */ 197 static uint_t 198 aio_iterations(void) 199 { 200 const char *workers = getenv("_AIO_MIN_WORKERS"); 201 const char *errstr; 202 long long val; 203 204 if (workers == NULL) 205 errx(EXIT_FAILURE, "TEST FAILED: _AIO_MIN_WORKERS is unset"); 206 207 val = strtonum(workers, 1, INT_MAX, &errstr); 208 if (errstr != NULL) { 209 errx(EXIT_FAILURE, "TEST FAILED: _AIO_MIN_WORKERS is %s, " 210 "which is %s; the worker pool size is not known", workers, 211 errstr); 212 } 213 214 if (val > AIO_WORKERS_WARN) 215 warnx("_AIO_MIN_WORKERS is %lld, so this run may take minutes", 216 val); 217 218 return ((2 * (uint_t)val) + 1); 219 } 220 221 int 222 main(int argc, char **argv) 223 { 224 struct ps_prochandle *P; 225 int fds[2]; 226 int perr; 227 uint_t stuck = 0; 228 uint_t iterations; 229 char buf[AIO_BUFSZ]; 230 231 /* 232 * Ensure that the number of AIO workers is known, so that our model of 233 * the AIO behavior is correct. 234 * 235 * libc reads _AIO_MIN_WORKERS in its init section, so the pool size 236 * must be set before this process starts. If the caller did not 237 * choose one, set our own and exec ourselves again. 238 */ 239 if (getenv("_AIO_MIN_WORKERS") == NULL) { 240 if (setenv("_AIO_MIN_WORKERS", AIO_STR(AIO_WORKERS), 1) != 0) 241 err(EXIT_FAILURE, "TEST FAILED: setenv"); 242 (void) execvp(argv[0], argv); 243 err(EXIT_FAILURE, "TEST FAILED: could not re-exec %s", 244 argv[0]); 245 } 246 247 iterations = aio_iterations(); 248 249 aio_notified = calloc(iterations, sizeof (*aio_notified)); 250 if (aio_notified == NULL) 251 err(EXIT_FAILURE, "TEST FAILED: calloc"); 252 253 /* 254 * A read-only grab is the only kind permitted on the calling process, 255 * and it is all Plwp_iter() needs. 256 */ 257 P = Pgrab(getpid(), PGRAB_RDONLY, &perr); 258 if (P == NULL) { 259 errx(EXIT_FAILURE, "TEST FAILED: could not grab ourselves: %s", 260 Pgrab_error(perr)); 261 } 262 263 if (pipe(fds) != 0) 264 err(EXIT_FAILURE, "TEST FAILED: could not create a pipe"); 265 266 if (aio_readers(P, fds[0]) != 0) { 267 errx(EXIT_FAILURE, "TEST FAILED: a thread is reading the pipe " 268 "before the first request"); 269 } 270 271 for (uint_t i = 0; i < iterations; i++) { 272 struct aiocb cb; 273 uint_t before; 274 uint_t try; 275 int error; 276 277 (void) memset(&cb, 0, sizeof (cb)); 278 cb.aio_fildes = fds[0]; 279 cb.aio_buf = buf; 280 cb.aio_nbytes = sizeof (buf); 281 cb.aio_sigevent.sigev_notify = SIGEV_THREAD; 282 cb.aio_sigevent.sigev_notify_function = aio_notify; 283 cb.aio_sigevent.sigev_value.sival_int = i; 284 285 /* Get a baseline count for this iteration. */ 286 before = aio_readers(P, fds[0]); 287 288 /* 289 * This read will block indefinitely. 290 * 291 * aio.c adds a worker only when it finds neither an idle 292 * worker nor an acquirable queue lock, which never happens 293 * with one request in flight, so the pool stays at the size 294 * requested above. 295 */ 296 if (aio_read(&cb) != 0) 297 err(EXIT_FAILURE, "TEST FAILED: aio_read %u", i); 298 299 /* Baseline + 1 means the request reached its own worker. */ 300 for (try = 0; try < AIO_SETTLE_TRIES; try++) { 301 if (aio_readers(P, fds[0]) > before) 302 break; 303 (void) nanosleep(&aio_poll, NULL); 304 } 305 if (try == AIO_SETTLE_TRIES) { 306 errx(EXIT_FAILURE, "TEST FAILED: request %u reached no " 307 "worker within %d ms, with %u already blocked in " 308 "read(2)", i, AIO_SETTLE_MS, before); 309 } 310 311 if (aio_cancel(fds[0], &cb) != AIO_CANCELED) { 312 warnx("TEST FAILED: request %u was not cancelled", i); 313 stuck++; 314 continue; 315 } 316 317 if (!aio_await(i)) { 318 warnx("TEST FAILED: request %u was cancelled but " 319 "never reported after %d ms", i, AIO_DEADLINE_MS); 320 stuck++; 321 continue; 322 } 323 324 /* 325 * aio_error() returns -1 when the aiocb names no outstanding 326 * request, which is a different failure from the request 327 * reporting the wrong status. 328 */ 329 error = aio_error(&cb); 330 if (error == -1) { 331 warnx("TEST FAILED: aio_error on request %u: %s", i, 332 strerror(errno)); 333 stuck++; 334 } else if (error != ECANCELED) { 335 warnx("TEST FAILED: request %u reported %s, expected " 336 "ECANCELED", i, strerror(error)); 337 stuck++; 338 } 339 } 340 341 /* 342 * Any unreported cancellation is a failure. A worker whose 343 * cancellation was not delivered is still blocked in read(2), so 344 * report that count as well. 345 */ 346 if (stuck != 0) { 347 errx(EXIT_FAILURE, "TEST FAILED: %u of %u cancellations were " 348 "not reported, %u workers left blocked in read(2)", stuck, 349 iterations, aio_readers(P, fds[0])); 350 } 351 352 Prelease(P, 0); 353 free(aio_notified); 354 355 (void) printf("TEST PASSED: %u cancellations were all reported\n", 356 iterations); 357 return (EXIT_SUCCESS); 358 } 359