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
32int
33do_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
198int
199do_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
257int
258do_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