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