xref: /freebsd/tests/sys/kqueue/libkqueue/timer.c (revision 5ab1c5846ff41be24b1f6beb0317bf8258cd4409)
1 /*
2  * Copyright (c) 2009 Mark Heily <mark@heily.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  *
16  * $FreeBSD$
17  */
18 
19 #include "common.h"
20 #include <sys/time.h>
21 
22 #define	MILLION 1000000
23 #define	THOUSAND 1000
24 #define	SEC_TO_MS(t) ((t) * THOUSAND)	/* Convert seconds to milliseconds. */
25 #define	SEC_TO_US(t) ((t) * MILLION)	/* Convert seconds to microseconds. */
26 #define	MS_TO_US(t)  ((t) * THOUSAND)	/* Convert milliseconds to microseconds. */
27 #define	US_TO_NS(t)  ((t) * THOUSAND)	/* Convert microseconds to nanoseconds. */
28 
29 int kqfd;
30 
31 /* Get the current time with microsecond precision. Used for
32  * sub-second timing to make some timer tests run faster.
33  */
34 static long
35 now(void)
36 {
37     struct timeval tv;
38 
39     gettimeofday(&tv, NULL);
40     return SEC_TO_US(tv.tv_sec) + tv.tv_usec;
41 }
42 
43 /* Sleep for a given number of milliseconds. The timeout is assumed to
44  * be less than 1 second.
45  */
46 void
47 mssleep(int t)
48 {
49     struct timespec stime = {
50         .tv_sec = 0,
51         .tv_nsec = US_TO_NS(MS_TO_US(t)),
52     };
53 
54     nanosleep(&stime, NULL);
55 }
56 
57 /* Sleep for a given number of microseconds. The timeout is assumed to
58  * be less than 1 second.
59  */
60 void
61 ussleep(int t)
62 {
63     struct timespec stime = {
64         .tv_sec = 0,
65         .tv_nsec = US_TO_NS(t),
66     };
67 
68     nanosleep(&stime, NULL);
69 }
70 
71 void
72 test_kevent_timer_add(void)
73 {
74     const char *test_id = "kevent(EVFILT_TIMER, EV_ADD)";
75     struct kevent kev;
76 
77     test_begin(test_id);
78 
79     EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);
80     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
81         err(1, "%s", test_id);
82 
83     success();
84 }
85 
86 void
87 test_kevent_timer_del(void)
88 {
89     const char *test_id = "kevent(EVFILT_TIMER, EV_DELETE)";
90     struct kevent kev;
91 
92     test_begin(test_id);
93 
94     EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
95     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
96         err(1, "%s", test_id);
97 
98     test_no_kevents();
99 
100     success();
101 }
102 
103 void
104 test_kevent_timer_get(void)
105 {
106     const char *test_id = "kevent(EVFILT_TIMER, wait)";
107     struct kevent kev;
108 
109     test_begin(test_id);
110 
111     EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);
112     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
113         err(1, "%s", test_id);
114 
115     kev.flags |= EV_CLEAR;
116     kev.data = 1;
117     kevent_cmp(&kev, kevent_get(kqfd));
118 
119     EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
120     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
121         err(1, "%s", test_id);
122 
123     success();
124 }
125 
126 static void
127 test_oneshot(void)
128 {
129     const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT)";
130     struct kevent kev;
131 
132     test_begin(test_id);
133 
134     test_no_kevents();
135 
136     EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 500,NULL);
137     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
138         err(1, "%s", test_id);
139 
140     /* Retrieve the event */
141     kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;
142     kev.data = 1;
143     kevent_cmp(&kev, kevent_get(kqfd));
144 
145     /* Check if the event occurs again */
146     sleep(3);
147     test_no_kevents();
148 
149 
150     success();
151 }
152 
153 static void
154 test_periodic(void)
155 {
156     const char *test_id = "kevent(EVFILT_TIMER, periodic)";
157     struct kevent kev;
158 
159     test_begin(test_id);
160 
161     test_no_kevents();
162 
163     EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000,NULL);
164     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
165         err(1, "%s", test_id);
166 
167     /* Retrieve the event */
168     kev.flags = EV_ADD | EV_CLEAR;
169     kev.data = 1;
170     kevent_cmp(&kev, kevent_get(kqfd));
171 
172     /* Check if the event occurs again */
173     sleep(1);
174     kevent_cmp(&kev, kevent_get(kqfd));
175 
176     /* Delete the event */
177     kev.flags = EV_DELETE;
178     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
179         err(1, "%s", test_id);
180 
181     success();
182 }
183 
184 static void
185 disable_and_enable(void)
186 {
187     const char *test_id = "kevent(EVFILT_TIMER, EV_DISABLE and EV_ENABLE)";
188     struct kevent kev;
189 
190     test_begin(test_id);
191 
192     test_no_kevents();
193 
194     /* Add the watch and immediately disable it */
195     EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 2000,NULL);
196     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
197         err(1, "%s", test_id);
198     kev.flags = EV_DISABLE;
199     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
200         err(1, "%s", test_id);
201     test_no_kevents();
202 
203     /* Re-enable and check again */
204     kev.flags = EV_ENABLE;
205     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
206         err(1, "%s", test_id);
207 
208     kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;
209     kev.data = 1;
210     kevent_cmp(&kev, kevent_get(kqfd));
211 
212     success();
213 }
214 
215 static void
216 test_abstime(void)
217 {
218     const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT, NOTE_ABSTIME)";
219     struct kevent kev;
220     time_t start;
221     time_t stop;
222     const int timeout = 3;
223 
224     test_begin(test_id);
225 
226     test_no_kevents();
227 
228     start = time(NULL);
229     EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
230       NOTE_ABSTIME | NOTE_SECONDS, start + timeout, NULL);
231     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
232         err(1, "%s", test_id);
233 
234     /* Retrieve the event */
235     kev.flags = EV_ADD | EV_ONESHOT;
236     kev.data = 1;
237     kev.fflags = 0;
238     kevent_cmp(&kev, kevent_get(kqfd));
239     stop = time(NULL);
240     if (stop < start + timeout)
241         err(1, "too early %jd %jd", (intmax_t)stop, (intmax_t)(start + timeout));
242 
243     /* Check if the event occurs again */
244     sleep(3);
245     test_no_kevents();
246 
247     success();
248 }
249 
250 static void
251 test_update(void)
252 {
253     const char *test_id = "kevent(EVFILT_TIMER (UPDATE), EV_ADD | EV_ONESHOT)";
254     struct kevent kev;
255     long elapsed;
256     long start;
257 
258     test_begin(test_id);
259 
260     test_no_kevents();
261 
262     /* First set the timer to 1 second */
263     EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
264         NOTE_USECONDS, SEC_TO_US(1), (void *)1);
265     start = now();
266     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
267         err(1, "%s", test_id);
268 
269     /* Now reduce the timer to 1 ms */
270     EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
271         NOTE_USECONDS, MS_TO_US(1), (void *)2);
272     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
273         err(1, "%s", test_id);
274 
275     /* Wait for the event */
276     kev.flags |= EV_CLEAR;
277     kev.fflags &= ~NOTE_USECONDS;
278     kev.data = 1;
279     kevent_cmp(&kev, kevent_get(kqfd));
280     elapsed = now() - start;
281 
282     /* Check that the timer expired after at least 1 ms, but less than
283      * 1 second. This check is to make sure that the original 1 second
284      * timeout was not used.
285      */
286     printf("timer expired after %ld us\n", elapsed);
287     if (elapsed < MS_TO_US(1))
288         errx(1, "early timer expiration: %ld us", elapsed);
289     if (elapsed > SEC_TO_US(1))
290         errx(1, "late timer expiration: %ld us", elapsed);
291 
292     success();
293 }
294 
295 static void
296 test_update_equal(void)
297 {
298     const char *test_id = "kevent(EVFILT_TIMER (UPDATE=), EV_ADD | EV_ONESHOT)";
299     struct kevent kev;
300     long elapsed;
301     long start;
302 
303     test_begin(test_id);
304 
305     test_no_kevents();
306 
307     /* First set the timer to 1 ms */
308     EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
309         NOTE_USECONDS, MS_TO_US(1), NULL);
310     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
311         err(1, "%s", test_id);
312 
313     /* Sleep for a significant fraction of the timeout. */
314     ussleep(600);
315 
316     /* Now re-add the timer with the same parameters */
317     start = now();
318     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
319         err(1, "%s", test_id);
320 
321     /* Wait for the event */
322     kev.flags |= EV_CLEAR;
323     kev.fflags &= ~NOTE_USECONDS;
324     kev.data = 1;
325     kevent_cmp(&kev, kevent_get(kqfd));
326     elapsed = now() - start;
327 
328     /* Check that the timer expired after at least 1 ms. This check is
329      * to make sure that the timer re-started and that the event is
330      * not from the original add of the timer.
331      */
332     printf("timer expired after %ld us\n", elapsed);
333     if (elapsed < MS_TO_US(1))
334         errx(1, "early timer expiration: %ld us", elapsed);
335 
336     success();
337 }
338 
339 static void
340 test_update_expired(void)
341 {
342     const char *test_id = "kevent(EVFILT_TIMER (UPDATE EXP), EV_ADD | EV_ONESHOT)";
343     struct kevent kev;
344     long elapsed;
345     long start;
346 
347     test_begin(test_id);
348 
349     test_no_kevents();
350 
351     /* Set the timer to 1ms */
352     EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
353         NOTE_USECONDS, MS_TO_US(1), NULL);
354     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
355         err(1, "%s", test_id);
356 
357     /* Wait for 2 ms to give the timer plenty of time to expire. */
358     mssleep(2);
359 
360     /* Now re-add the timer */
361     start = now();
362     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
363         err(1, "%s", test_id);
364 
365     /* Wait for the event */
366     kev.flags |= EV_CLEAR;
367     kev.fflags &= ~NOTE_USECONDS;
368     kev.data = 1;
369     kevent_cmp(&kev, kevent_get(kqfd));
370     elapsed = now() - start;
371 
372     /* Check that the timer expired after at least 1 ms.  This check
373      * is to make sure that the timer re-started and that the event is
374      * not from the original add (and expiration) of the timer.
375      */
376     printf("timer expired after %ld us\n", elapsed);
377     if (elapsed < MS_TO_US(1))
378         errx(1, "early timer expiration: %ld us", elapsed);
379 
380     /* Make sure the re-added timer does not fire. In other words,
381      * test that the event received above was the only event from the
382      * add and re-add of the timer.
383      */
384     mssleep(2);
385     test_no_kevents();
386 
387     success();
388 }
389 
390 static void
391 test_update_periodic(void)
392 {
393     const char *test_id = "kevent(EVFILT_TIMER (UPDATE), periodic)";
394     struct kevent kev;
395     long elapsed;
396     long start;
397     long stop;
398 
399     test_begin(test_id);
400 
401     test_no_kevents();
402 
403     EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, SEC_TO_MS(1), NULL);
404     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
405         err(1, "%s", test_id);
406 
407     /* Retrieve the event */
408     kev.flags = EV_ADD | EV_CLEAR;
409     kev.data = 1;
410     kevent_cmp(&kev, kevent_get(kqfd));
411 
412     /* Check if the event occurs again */
413     sleep(1);
414     kevent_cmp(&kev, kevent_get(kqfd));
415 
416     /* Re-add with new timeout. */
417     EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, SEC_TO_MS(2), NULL);
418     start = now();
419     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
420         err(1, "%s", test_id);
421 
422     /* Retrieve the event */
423     kev.flags = EV_ADD | EV_CLEAR;
424     kev.data = 1;
425     kevent_cmp(&kev, kevent_get(kqfd));
426 
427     stop = now();
428     elapsed = stop - start;
429 
430     /* Check that the timer expired after at least 2 ms.
431      */
432     printf("timer expired after %ld us\n", elapsed);
433     if (elapsed < MS_TO_US(2))
434         errx(1, "early timer expiration: %ld us", elapsed);
435 
436     /* Delete the event */
437     kev.flags = EV_DELETE;
438     if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
439         err(1, "%s", test_id);
440 
441     success();
442 }
443 
444 static void
445 test_update_timing(void)
446 {
447 #define	MIN_SLEEP 500
448 #define	MAX_SLEEP 1500
449     const char *test_id = "kevent(EVFILT_TIMER (UPDATE TIMING), EV_ADD | EV_ONESHOT)";
450     struct kevent kev;
451     int iteration;
452     int sleeptime;
453     long elapsed;
454     long start;
455     long stop;
456 
457     test_begin(test_id);
458 
459     test_no_kevents();
460 
461     /* Re-try the update tests with a variety of delays between the
462      * original timer activation and the update of the timer. The goal
463      * is to show that in all cases the only timer event that is
464      * received is from the update and not the original timer add.
465      */
466     for (sleeptime = MIN_SLEEP, iteration = 1;
467          sleeptime < MAX_SLEEP;
468          ++sleeptime, ++iteration) {
469 
470         /* First set the timer to 1 ms */
471         EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
472             NOTE_USECONDS, MS_TO_US(1), NULL);
473         if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
474             err(1, "%s", test_id);
475 
476         /* Delay; the delay ranges from less than to greater than the
477          * timer period.
478          */
479         ussleep(sleeptime);
480 
481         /* Now re-add the timer with the same parameters */
482         start = now();
483         if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
484             err(1, "%s", test_id);
485 
486         /* Wait for the event */
487         kev.flags |= EV_CLEAR;
488         kev.fflags &= ~NOTE_USECONDS;
489         kev.data = 1;
490         kevent_cmp(&kev, kevent_get(kqfd));
491         stop = now();
492         elapsed = stop - start;
493 
494         /* Check that the timer expired after at least 1 ms. This
495          * check is to make sure that the timer re-started and that
496          * the event is not from the original add of the timer.
497          */
498         if (elapsed < MS_TO_US(1))
499             errx(1, "early timer expiration: %ld us", elapsed);
500 
501         /* Make sure the re-added timer does not fire. In other words,
502          * test that the event received above was the only event from
503          * the add and re-add of the timer.
504          */
505         mssleep(2);
506         test_no_kevents_quietly();
507     }
508 
509     success();
510 }
511 
512 void
513 test_evfilt_timer()
514 {
515     kqfd = kqueue();
516     test_kevent_timer_add();
517     test_kevent_timer_del();
518     test_kevent_timer_get();
519     test_oneshot();
520     test_periodic();
521     test_abstime();
522     test_update();
523     test_update_equal();
524     test_update_expired();
525     test_update_timing();
526     test_update_periodic();
527     disable_and_enable();
528     close(kqfd);
529 }
530