1 /* $OpenBSD: kqueue-timer.c,v 1.5 2023/08/13 08:29:28 visa Exp $ */
2 /*
3 * Copyright (c) 2015 Bret Stephen Lambert <blambert@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <sys/types.h>
19 #include <sys/time.h>
20 #include <sys/event.h>
21
22 #include <err.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <string.h>
27 #include <time.h>
28 #include <unistd.h>
29
30 #include "main.h"
31
32 int
do_timer(void)33 do_timer(void)
34 {
35 static const int units[] = {
36 NOTE_SECONDS, NOTE_MSECONDS, NOTE_USECONDS, NOTE_NSECONDS
37 };
38 struct kevent ev;
39 struct timespec ts, start, end, now;
40 int64_t usecs;
41 int i, kq, n;
42
43 ASS((kq = kqueue()) >= 0,
44 warn("kqueue"));
45
46 memset(&ev, 0, sizeof(ev));
47 ev.filter = EVFILT_TIMER;
48 ev.flags = EV_ADD | EV_ENABLE | EV_ONESHOT;
49 ev.data = 500; /* 1/2 second in ms */
50
51 n = kevent(kq, &ev, 1, NULL, 0, NULL);
52 ASSX(n != -1);
53
54 ts.tv_sec = 2; /* wait 2s for kqueue timeout */
55 ts.tv_nsec = 0;
56
57 n = kevent(kq, NULL, 0, &ev, 1, &ts);
58 ASSX(n == 1);
59
60 /* Now retry w/o EV_ONESHOT, as EV_CLEAR is implicit */
61
62 memset(&ev, 0, sizeof(ev));
63 ev.filter = EVFILT_TIMER;
64 ev.flags = EV_ADD | EV_ENABLE;
65 ev.data = 500; /* 1/2 second in ms */
66
67 n = kevent(kq, &ev, 1, NULL, 0, NULL);
68 ASSX(n != -1);
69
70 ts.tv_sec = 2; /* wait 2s for kqueue timeout */
71 ts.tv_nsec = 0;
72
73 n = kevent(kq, NULL, 0, &ev, 1, &ts);
74 ASSX(n == 1);
75
76 /* Test with different time units */
77
78 for (i = 0; i < sizeof(units) / sizeof(units[0]); i++) {
79 memset(&ev, 0, sizeof(ev));
80 ev.filter = EVFILT_TIMER;
81 ev.flags = EV_ADD | EV_ENABLE;
82 ev.fflags = units[i];
83 ev.data = 1;
84
85 n = kevent(kq, &ev, 1, NULL, 0, NULL);
86 ASSX(n != -1);
87
88 ts.tv_sec = 2; /* wait 2s for kqueue timeout */
89 ts.tv_nsec = 0;
90
91 n = kevent(kq, NULL, 0, &ev, 1, &ts);
92 ASSX(n == 1);
93
94 /* Delete timer to clear EV_CLEAR */
95
96 memset(&ev, 0, sizeof(ev));
97 ev.filter = EVFILT_TIMER;
98 ev.flags = EV_DELETE;
99
100 n = kevent(kq, &ev, 1, NULL, 0, NULL);
101 ASSX(n != -1);
102
103 /* Test with NOTE_ABSTIME, deadline in the future */
104
105 clock_gettime(CLOCK_MONOTONIC, &start);
106
107 clock_gettime(CLOCK_REALTIME, &now);
108 memset(&ev, 0, sizeof(ev));
109 ev.filter = EVFILT_TIMER;
110 ev.flags = EV_ADD | EV_ENABLE;
111 ev.fflags = NOTE_ABSTIME | units[i];
112
113 switch (units[i]) {
114 case NOTE_SECONDS:
115 ev.data = now.tv_sec + 1;
116 break;
117 case NOTE_MSECONDS:
118 ev.data = now.tv_sec * 1000 + now.tv_nsec / 1000000
119 + 100;
120 break;
121 case NOTE_USECONDS:
122 ev.data = now.tv_sec * 1000000 + now.tv_nsec / 1000
123 + 100 * 1000;
124 break;
125 case NOTE_NSECONDS:
126 ev.data = now.tv_sec * 1000000000 + now.tv_nsec
127 + 100 * 1000000;
128 break;
129 }
130
131 n = kevent(kq, &ev, 1, NULL, 0, NULL);
132 ASSX(n != -1);
133
134 ts.tv_sec = 2; /* wait 2s for kqueue timeout */
135 ts.tv_nsec = 0;
136
137 n = kevent(kq, NULL, 0, &ev, 1, &ts);
138 ASSX(n == 1);
139
140 clock_gettime(CLOCK_MONOTONIC, &end);
141 timespecsub(&end, &start, &ts);
142 usecs = ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
143 ASSX(usecs > 0);
144 ASSX(usecs < 1500000); /* allow wide margin */
145
146 /* Test with NOTE_ABSTIME, deadline in the past. */
147
148 clock_gettime(CLOCK_MONOTONIC, &start);
149
150 memset(&ev, 0, sizeof(ev));
151 ev.filter = EVFILT_TIMER;
152 ev.flags = EV_ADD | EV_ENABLE;
153 ev.fflags = NOTE_ABSTIME | units[i];
154
155 clock_gettime(CLOCK_REALTIME, &now);
156 switch (units[i]) {
157 case NOTE_SECONDS:
158 ev.data = now.tv_sec - 1;
159 break;
160 case NOTE_MSECONDS:
161 ev.data = now.tv_sec * 1000 + now.tv_nsec / 1000000
162 - 100;
163 break;
164 case NOTE_USECONDS:
165 ev.data = now.tv_sec * 1000000 + now.tv_nsec / 1000
166 - 100 * 1000;
167 break;
168 case NOTE_NSECONDS:
169 ev.data = now.tv_sec * 1000000000 + now.tv_nsec
170 - 100 * 1000000;
171 break;
172 }
173
174 n = kevent(kq, &ev, 1, NULL, 0, NULL);
175 ASSX(n != -1);
176
177 n = kevent(kq, NULL, 0, &ev, 1, &ts);
178 ASSX(n == 1);
179
180 clock_gettime(CLOCK_MONOTONIC, &end);
181 timespecsub(&end, &start, &ts);
182 usecs = ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
183 ASSX(usecs > 0);
184 ASSX(usecs < 100000); /* allow wide margin */
185
186 /* Test that the event remains active */
187
188 ts.tv_sec = 2; /* wait 2s for kqueue timeout */
189 ts.tv_nsec = 0;
190
191 n = kevent(kq, NULL, 0, &ev, 1, &ts);
192 ASSX(n == 1);
193 }
194
195 return (0);
196 }
197
198 int
do_invalid_timer(void)199 do_invalid_timer(void)
200 {
201 int i, kq, n;
202 struct kevent ev;
203 struct timespec invalid_ts[3] = { {-1, 0}, {0, -1}, {0, 1000000000L} };
204
205 ASS((kq = kqueue()) >= 0,
206 warn("kqueue"));
207
208 memset(&ev, 0, sizeof(ev));
209 ev.filter = EVFILT_TIMER;
210 ev.flags = EV_ADD | EV_ENABLE;
211 ev.data = 500; /* 1/2 second in ms */
212
213 n = kevent(kq, &ev, 1, NULL, 0, NULL);
214 ASSX(n != -1);
215
216 for (i = 0; i < 3; i++) {
217 n = kevent(kq, NULL, 0, &ev, 1, &invalid_ts[i]);
218 ASS(n == -1 && errno == EINVAL,
219 warn("kevent: timeout %lld %ld",
220 (long long)invalid_ts[i].tv_sec, invalid_ts[i].tv_nsec));
221 }
222
223 /* Test invalid fflags */
224
225 memset(&ev, 0, sizeof(ev));
226 ev.filter = EVFILT_TIMER;
227 ev.flags = EV_ADD | EV_ENABLE;
228 ev.fflags = ~NOTE_SECONDS;
229 ev.data = 1;
230
231 n = kevent(kq, &ev, 1, NULL, 0, NULL);
232 ASSX(n == -1 && errno == EINVAL);
233
234 memset(&ev, 0, sizeof(ev));
235 ev.filter = EVFILT_TIMER;
236 ev.flags = EV_ADD | EV_ENABLE;
237 ev.fflags = NOTE_MSECONDS;
238 ev.data = 500;
239
240 n = kevent(kq, &ev, 1, NULL, 0, NULL);
241 ASSX(n == 0);
242
243 /* Modify the existing timer */
244
245 memset(&ev, 0, sizeof(ev));
246 ev.filter = EVFILT_TIMER;
247 ev.flags = EV_ADD | EV_ENABLE;
248 ev.fflags = ~NOTE_SECONDS;
249 ev.data = 1;
250
251 n = kevent(kq, &ev, 1, NULL, 0, NULL);
252 ASSX(n == -1 && errno == EINVAL);
253
254 return (0);
255 }
256
257 int
do_reset_timer(void)258 do_reset_timer(void)
259 {
260 int kq, msecs, n;
261 struct kevent ev;
262 struct timespec ts, start, end;
263
264 ASS((kq = kqueue()) >= 0,
265 warn("kqueue"));
266
267 clock_gettime(CLOCK_MONOTONIC, &start);
268
269 memset(&ev, 0, sizeof(ev));
270 ev.filter = EVFILT_TIMER;
271 ev.flags = EV_ADD | EV_ENABLE | EV_ONESHOT;
272 ev.data = 10;
273
274 n = kevent(kq, &ev, 1, NULL, 0, NULL);
275 ASSX(n != -1);
276
277 /* Let the timer expire. */
278 usleep(100000);
279
280 /* Reset the expired timer. */
281 ev.data = 60000;
282 n = kevent(kq, &ev, 1, NULL, 0, NULL);
283 ASSX(n != -1);
284
285 /* Check that no event is pending. */
286 ts.tv_sec = 0;
287 ts.tv_nsec = 0;
288 n = kevent(kq, NULL, 0, &ev, 1, &ts);
289 ASSX(n == 0);
290
291 /* Reset again for quick expiry. */
292 memset(&ev, 0, sizeof(ev));
293 ev.filter = EVFILT_TIMER;
294 ev.flags = EV_ADD | EV_ENABLE | EV_ONESHOT;
295 ev.data = 100;
296 n = kevent(kq, &ev, 1, NULL, 0, NULL);
297 ASSX(n != -1);
298
299 /* Wait for expiry. */
300 n = kevent(kq, NULL, 0, &ev, 1, NULL);
301 ASSX(n == 1);
302
303 clock_gettime(CLOCK_MONOTONIC, &end);
304 timespecsub(&end, &start, &ts);
305 msecs = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
306 ASSX(msecs > 200);
307 ASSX(msecs < 5000); /* allow wide margin */
308
309 return (0);
310 }
311