xref: /aosp_15_r20/external/liburing/test/multicqes_drain.c (revision 25da2bea747f3a93b4c30fd9708b0618ef55a0e6)
1 /* SPDX-License-Identifier: MIT */
2 /*
3  * Description: generic tests for  io_uring drain io
4  *
5  * The main idea is to randomly generate different type of sqe to
6  * challenge the drain logic. There are some restrictions for the
7  * generated sqes, details in io_uring maillist:
8  * https://lore.kernel.org/io-uring/[email protected]/
9  *
10  */
11 #include <errno.h>
12 #include <stdio.h>
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <time.h>
17 #include <poll.h>
18 
19 #include "liburing.h"
20 
21 enum {
22 	multi,
23 	single,
24 	nop,
25 	cancel,
26 	op_last,
27 };
28 
29 struct sqe_info {
30 	__u8 op;
31 	unsigned flags;
32 };
33 
34 #define max_entry 50
35 
36 /*
37  * sqe_flags: combination of sqe flags
38  * multi_sqes: record the user_data/index of all the multishot sqes
39  * cnt: how many entries there are in multi_sqes
40  * we can leverage multi_sqes array for cancellation: we randomly pick
41  * up an entry in multi_sqes when form a cancellation sqe.
42  * multi_cap: limitation of number of multishot sqes
43  */
44 const unsigned sqe_flags[4] = {0, IOSQE_IO_LINK, IOSQE_IO_DRAIN,
45 	IOSQE_IO_LINK | IOSQE_IO_DRAIN};
46 int multi_sqes[max_entry], cnt = 0;
47 int multi_cap = max_entry / 5;
48 
write_pipe(int pipe,char * str)49 int write_pipe(int pipe, char *str)
50 {
51 	int ret;
52 	do {
53 		errno = 0;
54 		ret = write(pipe, str, 3);
55 	} while (ret == -1 && errno == EINTR);
56 	return ret;
57 }
58 
read_pipe(int pipe)59 void read_pipe(int pipe)
60 {
61 	char str[4] = {0};
62 	int ret;
63 
64 	ret = read(pipe, &str, 3);
65 	if (ret < 0)
66 		perror("read");
67 }
68 
trigger_event(int p[])69 int trigger_event(int p[])
70 {
71 	int ret;
72 	if ((ret = write_pipe(p[1], "foo")) != 3) {
73 		fprintf(stderr, "bad write return %d\n", ret);
74 		return 1;
75 	}
76 	read_pipe(p[0]);
77 	return 0;
78 }
79 
io_uring_sqe_prep(int op,struct io_uring_sqe * sqe,unsigned sqe_flags,int arg)80 void io_uring_sqe_prep(int op, struct io_uring_sqe *sqe, unsigned sqe_flags, int arg)
81 {
82 	switch (op) {
83 		case multi:
84 			io_uring_prep_poll_add(sqe, arg, POLLIN);
85 			sqe->len |= IORING_POLL_ADD_MULTI;
86 			break;
87 		case single:
88 			io_uring_prep_poll_add(sqe, arg, POLLIN);
89 			break;
90 		case nop:
91 			io_uring_prep_nop(sqe);
92 			break;
93 		case cancel:
94 			io_uring_prep_poll_remove(sqe, arg);
95 			break;
96 	}
97 	sqe->flags = sqe_flags;
98 }
99 
generate_flags(int sqe_op)100 __u8 generate_flags(int sqe_op)
101 {
102 	__u8 flags = 0;
103 	/*
104 	 * drain sqe must be put after multishot sqes cancelled
105 	 */
106 	do {
107 		flags = sqe_flags[rand() % 4];
108 	} while ((flags & IOSQE_IO_DRAIN) && cnt);
109 
110 	/*
111 	 * cancel req cannot have drain or link flag
112 	 */
113 	if (sqe_op == cancel) {
114 		flags &= ~(IOSQE_IO_DRAIN | IOSQE_IO_LINK);
115 	}
116 	/*
117 	 * avoid below case:
118 	 * sqe0(multishot, link)->sqe1(nop, link)->sqe2(nop)->sqe3(cancel_sqe0)
119 	 * sqe3 may excute before sqe0 so that sqe0 isn't cancelled
120 	 */
121 	if (sqe_op == multi)
122 		flags &= ~IOSQE_IO_LINK;
123 
124 	return flags;
125 
126 }
127 
128 /*
129  * function to generate opcode of a sqe
130  * several restrictions here:
131  * - cancel all the previous multishot sqes as soon as possible when
132  *   we reach high watermark.
133  * - ensure there is some multishot sqe when generating a cancel sqe
134  * - ensure a cancel/multshot sqe is not in a linkchain
135  * - ensure number of multishot sqes doesn't exceed multi_cap
136  * - don't generate multishot sqes after high watermark
137  */
generate_opcode(int i,int pre_flags)138 int generate_opcode(int i, int pre_flags)
139 {
140 	int sqe_op;
141 	int high_watermark = max_entry - max_entry / 5;
142 	bool retry0 = false, retry1 = false, retry2 = false;
143 
144 	if ((i >= high_watermark) && cnt) {
145 		sqe_op = cancel;
146 	} else {
147 		do {
148 			sqe_op = rand() % op_last;
149 			retry0 = (sqe_op == cancel) && (!cnt || (pre_flags & IOSQE_IO_LINK));
150 			retry1 = (sqe_op == multi) && ((multi_cap - 1 < 0) || i >= high_watermark);
151 			retry2 = (sqe_op == multi) && (pre_flags & IOSQE_IO_LINK);
152 		} while (retry0 || retry1 || retry2);
153 	}
154 
155 	if (sqe_op == multi)
156 		multi_cap--;
157 	return sqe_op;
158 }
159 
add_multishot_sqe(int index)160 static inline void add_multishot_sqe(int index)
161 {
162 	multi_sqes[cnt++] = index;
163 }
164 
remove_multishot_sqe()165 int remove_multishot_sqe()
166 {
167 	int ret;
168 
169 	int rem_index = rand() % cnt;
170 	ret = multi_sqes[rem_index];
171 	multi_sqes[rem_index] = multi_sqes[cnt - 1];
172 	cnt--;
173 
174 	return ret;
175 }
176 
test_generic_drain(struct io_uring * ring)177 static int test_generic_drain(struct io_uring *ring)
178 {
179 	struct io_uring_cqe *cqe;
180 	struct io_uring_sqe *sqe[max_entry];
181 	struct sqe_info si[max_entry];
182 	int cqe_data[max_entry << 1], cqe_res[max_entry << 1];
183 	int i, j, ret, arg = 0;
184 	int pipes[max_entry][2];
185 	int pre_flags = 0;
186 
187 	for (i = 0; i < max_entry; i++) {
188 		if (pipe(pipes[i]) != 0) {
189 			perror("pipe");
190 			return 1;
191 		}
192 	}
193 
194 	srand((unsigned)time(NULL));
195 	for (i = 0; i < max_entry; i++) {
196 		sqe[i] = io_uring_get_sqe(ring);
197 		if (!sqe[i]) {
198 			printf("get sqe failed\n");
199 			goto err;
200 		}
201 
202 		int sqe_op = generate_opcode(i, pre_flags);
203 		__u8 flags = generate_flags(sqe_op);
204 
205 		if (sqe_op == cancel)
206 			arg = remove_multishot_sqe();
207 		if (sqe_op == multi || sqe_op == single)
208 			arg = pipes[i][0];
209 		io_uring_sqe_prep(sqe_op, sqe[i], flags, arg);
210 		sqe[i]->user_data = i;
211 		si[i].op = sqe_op;
212 		si[i].flags = flags;
213 		pre_flags = flags;
214 		if (sqe_op == multi)
215 			add_multishot_sqe(i);
216 	}
217 
218 	ret = io_uring_submit(ring);
219 	if (ret < 0) {
220 		printf("sqe submit failed\n");
221 		goto err;
222 	} else if (ret < max_entry) {
223 		printf("Submitted only %d\n", ret);
224 		goto err;
225 	}
226 
227 	sleep(1);
228 	// TODO: randomize event triggerring order
229 	for (i = 0; i < max_entry; i++) {
230 		if (si[i].op != multi && si[i].op != single)
231 			continue;
232 
233 		if (trigger_event(pipes[i]))
234 			goto err;
235 	}
236 	sleep(1);
237 	i = 0;
238 	while (!io_uring_peek_cqe(ring, &cqe)) {
239 		cqe_data[i] = cqe->user_data;
240 		cqe_res[i++] = cqe->res;
241 		io_uring_cqe_seen(ring, cqe);
242 	}
243 
244 	/*
245 	 * compl_bits is a bit map to record completions.
246 	 * eg. sqe[0], sqe[1], sqe[2] fully completed
247 	 * then compl_bits is 000...00111b
248 	 *
249 	 */
250 	unsigned long long compl_bits = 0;
251 	for (j = 0; j < i; j++) {
252 		int index = cqe_data[j];
253 		if ((si[index].flags & IOSQE_IO_DRAIN) && index) {
254 			if ((~compl_bits) & ((1ULL << index) - 1)) {
255 				printf("drain failed\n");
256 				goto err;
257 			}
258 		}
259 		/*
260 		 * for multishot sqes, record them only when it is cancelled
261 		 */
262 		if ((si[index].op != multi) || (cqe_res[j] == -ECANCELED))
263 			compl_bits |= (1ULL << index);
264 	}
265 
266 	return 0;
267 err:
268 	return 1;
269 }
270 
test_simple_drain(struct io_uring * ring)271 static int test_simple_drain(struct io_uring *ring)
272 {
273 	struct io_uring_cqe *cqe;
274 	struct io_uring_sqe *sqe[2];
275 	int i, ret;
276 	int pipe1[2], pipe2[2];
277 
278 	if (pipe(pipe1) != 0 || pipe(pipe2) != 0) {
279 		perror("pipe");
280 		return 1;
281 	}
282 
283 	for (i = 0; i < 2; i++) {
284 		sqe[i] = io_uring_get_sqe(ring);
285 		if (!sqe[i]) {
286 			printf("get sqe failed\n");
287 			goto err;
288 		}
289 	}
290 
291 	io_uring_prep_poll_multishot(sqe[0], pipe1[0], POLLIN);
292 	sqe[0]->user_data = 0;
293 
294 	io_uring_prep_poll_add(sqe[1], pipe2[0], POLLIN);
295 	sqe[1]->user_data = 1;
296 
297 	ret = io_uring_submit(ring);
298 	if (ret < 0) {
299 		printf("sqe submit failed\n");
300 		goto err;
301 	} else if (ret < 2) {
302 		printf("Submitted only %d\n", ret);
303 		goto err;
304 	}
305 
306 	for (i = 0; i < 2; i++) {
307 		if (trigger_event(pipe1))
308 			goto err;
309 	}
310 	if (trigger_event(pipe2))
311 			goto err;
312 
313 	for (i = 0; i < 2; i++) {
314 		sqe[i] = io_uring_get_sqe(ring);
315 		if (!sqe[i]) {
316 			printf("get sqe failed\n");
317 			goto err;
318 		}
319 	}
320 
321 	io_uring_prep_poll_remove(sqe[0], 0);
322 	sqe[0]->user_data = 2;
323 
324 	io_uring_prep_nop(sqe[1]);
325 	sqe[1]->flags |= IOSQE_IO_DRAIN;
326 	sqe[1]->user_data = 3;
327 
328 	ret = io_uring_submit(ring);
329 	if (ret < 0) {
330 		printf("sqe submit failed\n");
331 		goto err;
332 	} else if (ret < 2) {
333 		printf("Submitted only %d\n", ret);
334 		goto err;
335 	}
336 
337 	for (i = 0; i < 6; i++) {
338 		ret = io_uring_wait_cqe(ring, &cqe);
339 		if (ret < 0) {
340 			printf("wait completion %d\n", ret);
341 			goto err;
342 		}
343 		if ((i == 5) && (cqe->user_data != 3))
344 			goto err;
345 		io_uring_cqe_seen(ring, cqe);
346 	}
347 
348 	close(pipe1[0]);
349 	close(pipe1[1]);
350 	close(pipe2[0]);
351 	close(pipe2[1]);
352 	return 0;
353 err:
354 	return 1;
355 }
356 
main(int argc,char * argv[])357 int main(int argc, char *argv[])
358 {
359 	struct io_uring ring;
360 	int i, ret;
361 
362 	if (argc > 1)
363 		return 0;
364 
365 	ret = io_uring_queue_init(1024, &ring, 0);
366 	if (ret) {
367 		printf("ring setup failed\n");
368 		return 1;
369 	}
370 
371 	for (i = 0; i < 5; i++) {
372 		ret = test_simple_drain(&ring);
373 		if (ret) {
374 			fprintf(stderr, "test_simple_drain failed\n");
375 			break;
376 		}
377 	}
378 
379 	for (i = 0; i < 5; i++) {
380 		ret = test_generic_drain(&ring);
381 		if (ret) {
382 			fprintf(stderr, "test_generic_drain failed\n");
383 			break;
384 		}
385 	}
386 	return ret;
387 }
388