xref: /illumos-gate/usr/src/test/libc-tests/tests/aio_cancel.c (revision 438283cf397cce47d80cc67b04bbfdfe73b0d142)
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