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