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
19 #define NFILES 5000
20 #define BATCH 500
21 #define NLOOPS 1000
22
23 #define RING_SIZE 512
24
25 struct p {
26 int fd[2];
27 int triggered;
28 };
29
30 static struct p p[NFILES];
31
has_poll_update(void)32 static int has_poll_update(void)
33 {
34 struct io_uring ring;
35 struct io_uring_cqe *cqe;
36 struct io_uring_sqe *sqe;
37 bool has_update = false;
38 int ret;
39
40 ret = io_uring_queue_init(8, &ring, 0);
41 if (ret)
42 return -1;
43
44 sqe = io_uring_get_sqe(&ring);
45 io_uring_prep_poll_update(sqe, 0, 0, POLLIN, IORING_TIMEOUT_UPDATE);
46
47 ret = io_uring_submit(&ring);
48 if (ret != 1)
49 return -1;
50
51 ret = io_uring_wait_cqe(&ring, &cqe);
52 if (!ret) {
53 if (cqe->res == -ENOENT)
54 has_update = true;
55 else if (cqe->res != -EINVAL)
56 return -1;
57 io_uring_cqe_seen(&ring, cqe);
58 }
59 io_uring_queue_exit(&ring);
60 return has_update;
61 }
62
arm_poll(struct io_uring * ring,int off)63 static int arm_poll(struct io_uring *ring, int off)
64 {
65 struct io_uring_sqe *sqe;
66
67 sqe = io_uring_get_sqe(ring);
68 if (!sqe) {
69 fprintf(stderr, "failed getting sqe\n");
70 return 1;
71 }
72
73 io_uring_prep_poll_multishot(sqe, p[off].fd[0], POLLIN);
74 sqe->user_data = off;
75 return 0;
76 }
77
reap_polls(struct io_uring * ring)78 static int reap_polls(struct io_uring *ring)
79 {
80 struct io_uring_cqe *cqe;
81 int i, ret, off;
82 char c;
83
84 for (i = 0; i < BATCH; i++) {
85 struct io_uring_sqe *sqe;
86
87 sqe = io_uring_get_sqe(ring);
88 /* update event */
89 io_uring_prep_poll_update(sqe, i, 0, POLLIN,
90 IORING_POLL_UPDATE_EVENTS);
91 sqe->user_data = 0x12345678;
92 }
93
94 ret = io_uring_submit(ring);
95 if (ret != BATCH) {
96 fprintf(stderr, "submitted %d, %d\n", ret, BATCH);
97 return 1;
98 }
99
100 for (i = 0; i < 2 * BATCH; i++) {
101 ret = io_uring_wait_cqe(ring, &cqe);
102 if (ret) {
103 fprintf(stderr, "wait cqe %d\n", ret);
104 return ret;
105 }
106 off = cqe->user_data;
107 if (off == 0x12345678)
108 goto seen;
109 ret = read(p[off].fd[0], &c, 1);
110 if (ret != 1) {
111 if (ret == -1 && errno == EAGAIN)
112 goto seen;
113 fprintf(stderr, "read got %d/%d\n", ret, errno);
114 break;
115 }
116 seen:
117 io_uring_cqe_seen(ring, cqe);
118 }
119
120 if (i != 2 * BATCH) {
121 fprintf(stderr, "gave up at %d\n", i);
122 return 1;
123 }
124
125 return 0;
126 }
127
trigger_polls(void)128 static int trigger_polls(void)
129 {
130 char c = 89;
131 int i, ret;
132
133 for (i = 0; i < BATCH; i++) {
134 int off;
135
136 do {
137 off = rand() % NFILES;
138 if (!p[off].triggered)
139 break;
140 } while (1);
141
142 p[off].triggered = 1;
143 ret = write(p[off].fd[1], &c, 1);
144 if (ret != 1) {
145 fprintf(stderr, "write got %d/%d\n", ret, errno);
146 return 1;
147 }
148 }
149
150 return 0;
151 }
152
trigger_polls_fn(void * data)153 static void *trigger_polls_fn(void *data)
154 {
155 trigger_polls();
156 return NULL;
157 }
158
arm_polls(struct io_uring * ring)159 static int arm_polls(struct io_uring *ring)
160 {
161 int ret, to_arm = NFILES, i, off;
162
163 off = 0;
164 while (to_arm) {
165 int this_arm;
166
167 this_arm = to_arm;
168 if (this_arm > RING_SIZE)
169 this_arm = RING_SIZE;
170
171 for (i = 0; i < this_arm; i++) {
172 if (arm_poll(ring, off)) {
173 fprintf(stderr, "arm failed at %d\n", off);
174 return 1;
175 }
176 off++;
177 }
178
179 ret = io_uring_submit(ring);
180 if (ret != this_arm) {
181 fprintf(stderr, "submitted %d, %d\n", ret, this_arm);
182 return 1;
183 }
184 to_arm -= this_arm;
185 }
186
187 return 0;
188 }
189
main(int argc,char * argv[])190 int main(int argc, char *argv[])
191 {
192 struct io_uring ring;
193 struct io_uring_params params = { };
194 struct rlimit rlim;
195 pthread_t thread;
196 int i, j, ret;
197
198 if (argc > 1)
199 return 0;
200
201 ret = has_poll_update();
202 if (ret < 0) {
203 fprintf(stderr, "poll update check failed %i\n", ret);
204 return -1;
205 } else if (!ret) {
206 fprintf(stderr, "no poll update, skip\n");
207 return 0;
208 }
209
210 if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
211 perror("getrlimit");
212 goto err_noring;
213 }
214
215 if (rlim.rlim_cur < (2 * NFILES + 5)) {
216 rlim.rlim_cur = (2 * NFILES + 5);
217 rlim.rlim_max = rlim.rlim_cur;
218 if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) {
219 if (errno == EPERM)
220 goto err_nofail;
221 perror("setrlimit");
222 goto err_noring;
223 }
224 }
225
226 for (i = 0; i < NFILES; i++) {
227 if (pipe(p[i].fd) < 0) {
228 perror("pipe");
229 goto err_noring;
230 }
231 fcntl(p[i].fd[0], F_SETFL, O_NONBLOCK);
232 }
233
234 params.flags = IORING_SETUP_CQSIZE;
235 params.cq_entries = 4096;
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 return 0;
264 err:
265 io_uring_queue_exit(&ring);
266 err_noring:
267 fprintf(stderr, "poll-many failed\n");
268 return 1;
269 err_nofail:
270 fprintf(stderr, "poll-many: not enough files available (and not root), "
271 "skipped\n");
272 return 0;
273 }
274