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