1 /* SPDX-License-Identifier: MIT */
2 /*
3 * Description: test many files being polled for and updated
4 *
5 */
6 #include <errno.h>
7 #include <stdio.h>
8 #include <unistd.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <signal.h>
12 #include <poll.h>
13 #include <sys/resource.h>
14 #include <fcntl.h>
15 #include <pthread.h>
16
17 #include "liburing.h"
18 #include "helpers.h"
19
20 #define NFILES 5000
21 #define BATCH 500
22 #define NLOOPS 1000
23
24 static int nfiles = NFILES;
25
26 #define RING_SIZE 512
27
28 struct p {
29 int fd[2];
30 int triggered;
31 };
32
33 static struct p p[NFILES];
34
has_poll_update(void)35 static int has_poll_update(void)
36 {
37 struct io_uring ring;
38 struct io_uring_cqe *cqe;
39 struct io_uring_sqe *sqe;
40 bool has_update = false;
41 int ret;
42
43 ret = io_uring_queue_init(8, &ring, 0);
44 if (ret)
45 return -1;
46
47 sqe = io_uring_get_sqe(&ring);
48 io_uring_prep_poll_update(sqe, 0, 0, POLLIN, IORING_TIMEOUT_UPDATE);
49
50 ret = io_uring_submit(&ring);
51 if (ret != 1)
52 return -1;
53
54 ret = io_uring_wait_cqe(&ring, &cqe);
55 if (!ret) {
56 if (cqe->res == -ENOENT)
57 has_update = true;
58 else if (cqe->res != -EINVAL)
59 return -1;
60 io_uring_cqe_seen(&ring, cqe);
61 }
62 io_uring_queue_exit(&ring);
63 return has_update;
64 }
65
arm_poll(struct io_uring * ring,int off)66 static int arm_poll(struct io_uring *ring, int off)
67 {
68 struct io_uring_sqe *sqe;
69
70 sqe = io_uring_get_sqe(ring);
71 if (!sqe) {
72 fprintf(stderr, "failed getting sqe\n");
73 return 1;
74 }
75
76 io_uring_prep_poll_multishot(sqe, p[off].fd[0], POLLIN);
77 sqe->user_data = off;
78 return 0;
79 }
80
submit_arm_poll(struct io_uring * ring,int off)81 static int submit_arm_poll(struct io_uring *ring, int off)
82 {
83 int ret;
84
85 ret = arm_poll(ring, off);
86 if (ret)
87 return ret;
88
89 ret = io_uring_submit(ring);
90 if (ret < 0)
91 return ret;
92 return ret == 1 ? 0 : -1;
93 }
94
reap_polls(struct io_uring * ring)95 static int reap_polls(struct io_uring *ring)
96 {
97 struct io_uring_cqe *cqe;
98 int i, ret, off;
99 char c;
100
101 for (i = 0; i < BATCH; i++) {
102 struct io_uring_sqe *sqe;
103
104 sqe = io_uring_get_sqe(ring);
105 /* update event */
106 io_uring_prep_poll_update(sqe, i, 0, POLLIN,
107 IORING_POLL_UPDATE_EVENTS);
108 sqe->user_data = 0x12345678;
109 }
110
111 ret = io_uring_submit(ring);
112 if (ret != BATCH) {
113 fprintf(stderr, "submitted %d, %d\n", ret, BATCH);
114 return 1;
115 }
116
117 for (i = 0; i < 2 * BATCH; i++) {
118 ret = io_uring_wait_cqe(ring, &cqe);
119 if (ret) {
120 fprintf(stderr, "wait cqe %d\n", ret);
121 return ret;
122 }
123 off = cqe->user_data;
124 if (off == 0x12345678)
125 goto seen;
126 if (!(cqe->flags & IORING_CQE_F_MORE)) {
127 /* need to re-arm poll */
128 ret = submit_arm_poll(ring, off);
129 if (ret)
130 break;
131 if (cqe->res <= 0) {
132 /* retry this one */
133 i--;
134 goto seen;
135 }
136 }
137
138 ret = read(p[off].fd[0], &c, 1);
139 if (ret != 1) {
140 if (ret == -1 && errno == EAGAIN)
141 goto seen;
142 fprintf(stderr, "read got %d/%d\n", ret, errno);
143 break;
144 }
145 seen:
146 io_uring_cqe_seen(ring, cqe);
147 }
148
149 if (i != 2 * BATCH) {
150 fprintf(stderr, "gave up at %d\n", i);
151 return 1;
152 }
153
154 return 0;
155 }
156
trigger_polls(void)157 static int trigger_polls(void)
158 {
159 char c = 89;
160 int i, ret;
161
162 for (i = 0; i < BATCH; i++) {
163 int off;
164
165 do {
166 off = rand() % nfiles;
167 if (!p[off].triggered)
168 break;
169 } while (1);
170
171 p[off].triggered = 1;
172 ret = write(p[off].fd[1], &c, 1);
173 if (ret != 1) {
174 fprintf(stderr, "write got %d/%d\n", ret, errno);
175 return 1;
176 }
177 }
178
179 return 0;
180 }
181
trigger_polls_fn(void * data)182 static void *trigger_polls_fn(void *data)
183 {
184 trigger_polls();
185 return NULL;
186 }
187
arm_polls(struct io_uring * ring)188 static int arm_polls(struct io_uring *ring)
189 {
190 int ret, to_arm = nfiles, i, off;
191
192 off = 0;
193 while (to_arm) {
194 int this_arm;
195
196 this_arm = to_arm;
197 if (this_arm > RING_SIZE)
198 this_arm = RING_SIZE;
199
200 for (i = 0; i < this_arm; i++) {
201 if (arm_poll(ring, off)) {
202 fprintf(stderr, "arm failed at %d\n", off);
203 return 1;
204 }
205 off++;
206 }
207
208 ret = io_uring_submit(ring);
209 if (ret != this_arm) {
210 fprintf(stderr, "submitted %d, %d\n", ret, this_arm);
211 return 1;
212 }
213 to_arm -= this_arm;
214 }
215
216 return 0;
217 }
218
run(int cqe)219 static int run(int cqe)
220 {
221 struct io_uring ring;
222 struct io_uring_params params = { };
223 pthread_t thread;
224 int i, j, ret;
225
226 for (i = 0; i < nfiles; i++) {
227 if (pipe(p[i].fd) < 0) {
228 perror("pipe");
229 return 1;
230 }
231 fcntl(p[i].fd[0], F_SETFL, O_NONBLOCK);
232 }
233
234 params.flags = IORING_SETUP_CQSIZE;
235 params.cq_entries = cqe;
236 ret = io_uring_queue_init_params(RING_SIZE, &ring, ¶ms);
237 if (ret) {
238 if (ret == -EINVAL) {
239 fprintf(stdout, "No CQSIZE, trying without\n");
240 ret = io_uring_queue_init(RING_SIZE, &ring, 0);
241 if (ret) {
242 fprintf(stderr, "ring setup failed: %d\n", ret);
243 return 1;
244 }
245 }
246 }
247
248 if (arm_polls(&ring))
249 goto err;
250
251 for (i = 0; i < NLOOPS; i++) {
252 pthread_create(&thread, NULL, trigger_polls_fn, NULL);
253 ret = reap_polls(&ring);
254 if (ret)
255 goto err;
256 pthread_join(thread, NULL);
257
258 for (j = 0; j < nfiles; j++)
259 p[j].triggered = 0;
260 }
261
262 io_uring_queue_exit(&ring);
263 for (i = 0; i < nfiles; i++) {
264 close(p[i].fd[0]);
265 close(p[i].fd[1]);
266 }
267 return 0;
268 err:
269 io_uring_queue_exit(&ring);
270 return 1;
271 }
272
main(int argc,char * argv[])273 int main(int argc, char *argv[])
274 {
275 struct rlimit rlim;
276 int ret;
277
278 if (argc > 1)
279 return T_EXIT_SKIP;
280
281 ret = has_poll_update();
282 if (ret < 0) {
283 fprintf(stderr, "poll update check failed %i\n", ret);
284 return -1;
285 } else if (!ret) {
286 fprintf(stderr, "no poll update, skip\n");
287 return 0;
288 }
289
290 if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
291 perror("getrlimit");
292 goto err;
293 }
294
295 if (rlim.rlim_cur < (2 * NFILES + 5)) {
296 rlim.rlim_cur = rlim.rlim_max;
297 nfiles = (rlim.rlim_cur / 2) - 5;
298 if (nfiles > NFILES)
299 nfiles = NFILES;
300 if (nfiles <= 0)
301 goto err_nofail;
302 if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) {
303 if (errno == EPERM)
304 goto err_nofail;
305 perror("setrlimit");
306 return T_EXIT_FAIL;
307 }
308 }
309
310 ret = run(1024);
311 if (ret) {
312 fprintf(stderr, "run(1024) failed\n");
313 goto err;
314 }
315
316 ret = run(8192);
317 if (ret) {
318 fprintf(stderr, "run(8192) failed\n");
319 goto err;
320 }
321
322 return 0;
323 err:
324 fprintf(stderr, "poll-many failed\n");
325 return 1;
326 err_nofail:
327 fprintf(stderr, "poll-many: not enough files available (and not root), "
328 "skipped\n");
329 return T_EXIT_SKIP;
330 }
331