1 /* SPDX-License-Identifier: MIT */
2 /*
3 * Description: tests for getevents timeout
4 *
5 */
6 #include <stdio.h>
7 #include <sys/time.h>
8 #include <unistd.h>
9 #include <pthread.h>
10 #include "liburing.h"
11
12 #define TIMEOUT_MSEC 200
13 #define TIMEOUT_SEC 10
14
15 static int thread_ret0, thread_ret1;
16 static int cnt = 0;
17 static pthread_mutex_t mutex;
18
msec_to_ts(struct __kernel_timespec * ts,unsigned int msec)19 static void msec_to_ts(struct __kernel_timespec *ts, unsigned int msec)
20 {
21 ts->tv_sec = msec / 1000;
22 ts->tv_nsec = (msec % 1000) * 1000000;
23 }
24
mtime_since(const struct timeval * s,const struct timeval * e)25 static unsigned long long mtime_since(const struct timeval *s,
26 const struct timeval *e)
27 {
28 long long sec, usec;
29
30 sec = e->tv_sec - s->tv_sec;
31 usec = (e->tv_usec - s->tv_usec);
32 if (sec > 0 && usec < 0) {
33 sec--;
34 usec += 1000000;
35 }
36
37 sec *= 1000;
38 usec /= 1000;
39 return sec + usec;
40 }
41
mtime_since_now(struct timeval * tv)42 static unsigned long long mtime_since_now(struct timeval *tv)
43 {
44 struct timeval end;
45
46 gettimeofday(&end, NULL);
47 return mtime_since(tv, &end);
48 }
49
50
test_return_before_timeout(struct io_uring * ring)51 static int test_return_before_timeout(struct io_uring *ring)
52 {
53 struct io_uring_cqe *cqe;
54 struct io_uring_sqe *sqe;
55 int ret;
56 bool retried = false;
57 struct __kernel_timespec ts;
58
59 msec_to_ts(&ts, TIMEOUT_MSEC);
60
61 sqe = io_uring_get_sqe(ring);
62 io_uring_prep_nop(sqe);
63
64 ret = io_uring_submit(ring);
65 if (ret <= 0) {
66 fprintf(stderr, "%s: sqe submit failed: %d\n", __FUNCTION__, ret);
67 return 1;
68 }
69
70 again:
71 ret = io_uring_wait_cqe_timeout(ring, &cqe, &ts);
72 if (ret == -ETIME && (ring->flags & IORING_SETUP_SQPOLL) && !retried) {
73 /*
74 * there is a small chance SQPOLL hasn't been waked up yet,
75 * give it one more try.
76 */
77 printf("warning: funky SQPOLL timing\n");
78 sleep(1);
79 retried = true;
80 goto again;
81 } else if (ret < 0) {
82 fprintf(stderr, "%s: timeout error: %d\n", __FUNCTION__, ret);
83 return 1;
84 }
85 io_uring_cqe_seen(ring, cqe);
86 return 0;
87 }
88
test_return_after_timeout(struct io_uring * ring)89 static int test_return_after_timeout(struct io_uring *ring)
90 {
91 struct io_uring_cqe *cqe;
92 int ret;
93 struct __kernel_timespec ts;
94 struct timeval tv;
95 unsigned long long exp;
96
97 msec_to_ts(&ts, TIMEOUT_MSEC);
98 gettimeofday(&tv, NULL);
99 ret = io_uring_wait_cqe_timeout(ring, &cqe, &ts);
100 exp = mtime_since_now(&tv);
101 if (ret != -ETIME) {
102 fprintf(stderr, "%s: timeout error: %d\n", __FUNCTION__, ret);
103 return 1;
104 }
105
106 if (exp < TIMEOUT_MSEC / 2 || exp > (TIMEOUT_MSEC * 3) / 2) {
107 fprintf(stderr, "%s: Timeout seems wonky (got %llu)\n", __FUNCTION__, exp);
108 return 1;
109 }
110
111 return 0;
112 }
113
__reap_thread_fn(void * data)114 static int __reap_thread_fn(void *data)
115 {
116 struct io_uring *ring = (struct io_uring *)data;
117 struct io_uring_cqe *cqe;
118 struct __kernel_timespec ts;
119
120 msec_to_ts(&ts, TIMEOUT_SEC);
121 pthread_mutex_lock(&mutex);
122 cnt++;
123 pthread_mutex_unlock(&mutex);
124 return io_uring_wait_cqe_timeout(ring, &cqe, &ts);
125 }
126
reap_thread_fn0(void * data)127 static void *reap_thread_fn0(void *data)
128 {
129 thread_ret0 = __reap_thread_fn(data);
130 return NULL;
131 }
132
reap_thread_fn1(void * data)133 static void *reap_thread_fn1(void *data)
134 {
135 thread_ret1 = __reap_thread_fn(data);
136 return NULL;
137 }
138
139 /*
140 * This is to test issuing a sqe in main thread and reaping it in two child-thread
141 * at the same time. To see if timeout feature works or not.
142 */
test_multi_threads_timeout(void)143 static int test_multi_threads_timeout(void)
144 {
145 struct io_uring ring;
146 int ret;
147 bool both_wait = false;
148 pthread_t reap_thread0, reap_thread1;
149 struct io_uring_sqe *sqe;
150
151 ret = io_uring_queue_init(8, &ring, 0);
152 if (ret) {
153 fprintf(stderr, "%s: ring setup failed: %d\n", __FUNCTION__, ret);
154 return 1;
155 }
156
157 pthread_create(&reap_thread0, NULL, reap_thread_fn0, &ring);
158 pthread_create(&reap_thread1, NULL, reap_thread_fn1, &ring);
159
160 /*
161 * make two threads both enter io_uring_wait_cqe_timeout() before issuing the sqe
162 * as possible as we can. So that there are two threads in the ctx->wait queue.
163 * In this way, we can test if a cqe wakes up two threads at the same time.
164 */
165 while(!both_wait) {
166 pthread_mutex_lock(&mutex);
167 if (cnt == 2)
168 both_wait = true;
169 pthread_mutex_unlock(&mutex);
170 sleep(1);
171 }
172
173 sqe = io_uring_get_sqe(&ring);
174 if (!sqe) {
175 fprintf(stderr, "%s: get sqe failed\n", __FUNCTION__);
176 goto err;
177 }
178
179 io_uring_prep_nop(sqe);
180
181 ret = io_uring_submit(&ring);
182 if (ret <= 0) {
183 fprintf(stderr, "%s: sqe submit failed: %d\n", __FUNCTION__, ret);
184 goto err;
185 }
186
187 pthread_join(reap_thread0, NULL);
188 pthread_join(reap_thread1, NULL);
189
190 if ((thread_ret0 && thread_ret0 != -ETIME) || (thread_ret1 && thread_ret1 != -ETIME)) {
191 fprintf(stderr, "%s: thread wait cqe timeout failed: %d %d\n",
192 __FUNCTION__, thread_ret0, thread_ret1);
193 goto err;
194 }
195
196 return 0;
197 err:
198 return 1;
199 }
200
main(int argc,char * argv[])201 int main(int argc, char *argv[])
202 {
203 struct io_uring ring_normal, ring_sq;
204 int ret;
205
206 if (argc > 1)
207 return 0;
208
209 ret = io_uring_queue_init(8, &ring_normal, 0);
210 if (ret) {
211 fprintf(stderr, "ring_normal setup failed: %d\n", ret);
212 return 1;
213 }
214 if (!(ring_normal.features & IORING_FEAT_EXT_ARG)) {
215 fprintf(stderr, "feature IORING_FEAT_EXT_ARG not supported, skipping.\n");
216 return 0;
217 }
218
219 ret = test_return_before_timeout(&ring_normal);
220 if (ret) {
221 fprintf(stderr, "ring_normal: test_return_before_timeout failed\n");
222 return ret;
223 }
224
225 ret = test_return_after_timeout(&ring_normal);
226 if (ret) {
227 fprintf(stderr, "ring_normal: test_return_after_timeout failed\n");
228 return ret;
229 }
230
231 ret = io_uring_queue_init(8, &ring_sq, IORING_SETUP_SQPOLL);
232 if (ret) {
233 fprintf(stderr, "ring_sq setup failed: %d\n", ret);
234 return 1;
235 }
236
237 ret = test_return_before_timeout(&ring_sq);
238 if (ret) {
239 fprintf(stderr, "ring_sq: test_return_before_timeout failed\n");
240 return ret;
241 }
242
243 ret = test_return_after_timeout(&ring_sq);
244 if (ret) {
245 fprintf(stderr, "ring_sq: test_return_after_timeout failed\n");
246 return ret;
247 }
248
249 ret = test_multi_threads_timeout();
250 if (ret) {
251 fprintf(stderr, "test_multi_threads_timeout failed\n");
252 return ret;
253 }
254
255 return 0;
256 }
257