On 22/09/10 01:53PM, Visa Hankala wrote:
> On Wed, Aug 31, 2022 at 04:48:37PM -0400, aisha wrote:
> > I've added a patch which adds support for NOTE_{,U,M,N}SECONDS for
> > EVFILT_TIMER in the kqueue interface.
>
> It sort of makes sense to add an option to specify timeouts in
> sub-millisecond precision. It feels complete overengineering to add
> multiple time units on the level of the kernel interface. However,
> it looks that FreeBSD and NetBSD have already done this following
> macOS' lead...
>
> > I've also added the NOTE_ABSTIME but haven't done any actual implementation
> > there as I am not sure how the `data` field should be interpreted (is it
> > absolute time in seconds since epoch?).
>
> I think FreeBSD and NetBSD take NOTE_ABSTIME as time since the epoch.
>
> Below is a revised patch that takes into account some corner cases.
> It tries to be API-compatible with FreeBSD and NetBSD. I have adjusted
> the NOTE_{,M,U,N}SECONDS flags so that they are enum-like.
>
> The manual page bits are from NetBSD.
>
> It is quite late to introduce a feature like this within this release
> cycle. Until now, the timer code has ignored the fflags field. There
> might be pieces of software that are careless with struct kevent and
> that would break as a result of this patch. Programs that are widely
> used on different BSDs are probably fine already, though.
>
Sorry, I had forgotten this patch for a long time!!! I've been running with
this for a while now and it's been working nicely.
OK aisha@
I had an unrelated question inlined.
> Index: lib/libc/sys/kqueue.2
> ===================================================================
> RCS file: src/lib/libc/sys/kqueue.2,v
> retrieving revision 1.46
> diff -u -p -r1.46 kqueue.2
> --- lib/libc/sys/kqueue.2 31 Mar 2022 17:27:16 -0000 1.46
> +++ lib/libc/sys/kqueue.2 10 Sep 2022 13:01:36 -0000
> @@ -457,17 +457,71 @@ Establishes an arbitrary timer identifie
> .Fa ident .
> When adding a timer,
> .Fa data
> -specifies the timeout period in milliseconds.
> -The timer will be periodic unless
> +specifies the timeout period in units described below, or, if
> +.Dv NOTE_ABSTIME
> +is set in
> +.Va fflags ,
> +specifies the absolute time at which the timer should fire.
> +The timer will repeat unless
> .Dv EV_ONESHOT
> -is specified.
> +is set in
> +.Va flags
> +or
> +.Dv NOTE_ABSTIME
> +is set in
> +.Va fflags .
> On return,
> .Fa data
> contains the number of times the timeout has expired since the last call to
> .Fn kevent .
> -This filter automatically sets the
> +This filter automatically sets
> .Dv EV_CLEAR
> -flag internally.
> +in
> +.Va flags
> +for periodic timers.
> +Timers created with
> +.Dv NOTE_ABSTIME
> +remain activated on the kqueue once the absolute time has passed unless
> +.Dv EV_CLEAR
> +or
> +.Dv EV_ONESHOT
> +are also specified.
> +.Pp
> +The filter accepts the following flags in the
> +.Va fflags
> +argument:
> +.Bl -tag -width NOTE_MSECONDS
> +.It Dv NOTE_SECONDS
> +The timer value in
> +.Va data
> +is expressed in seconds.
> +.It Dv NOTE_MSECONDS
> +The timer value in
> +.Va data
> +is expressed in milliseconds.
> +.It Dv NOTE_USECONDS
> +The timer value in
> +.Va data
> +is expressed in microseconds.
> +.It Dv NOTE_NSECONDS
> +The timer value in
> +.Va data
> +is expressed in nanoseconds.
> +.It Dv NOTE_ABSTIME
> +The timer value is an absolute time with
> +.Dv CLOCK_REALTIME
> +as the reference clock.
> +.El
> +.Pp
> +Note that
> +.Dv NOTE_SECONDS ,
> +.Dv NOTE_MSECONDS ,
> +.Dv NOTE_USECONDS ,
> +and
> +.Dv NOTE_NSECONDS
> +are mutually exclusive; behavior is undefined if more than one are specified.
> +If a timer value unit is not specified, the default is
> +.Dv NOTE_MSECONDS .
> .Pp
> If an existing timer is re-added, the existing timer and related pending
> events
> will be cancelled.
> @@ -557,6 +611,7 @@ No memory was available to register the
> The specified process to attach to does not exist.
> .El
> .Sh SEE ALSO
> +.Xr clock_gettime 2 ,
> .Xr poll 2 ,
> .Xr read 2 ,
> .Xr select 2 ,
> Index: regress/sys/kern/kqueue/kqueue-timer.c
> ===================================================================
> RCS file: src/regress/sys/kern/kqueue/kqueue-timer.c,v
> retrieving revision 1.4
> diff -u -p -r1.4 kqueue-timer.c
> --- regress/sys/kern/kqueue/kqueue-timer.c 12 Jun 2021 13:30:14 -0000
> 1.4
> +++ regress/sys/kern/kqueue/kqueue-timer.c 10 Sep 2022 13:01:37 -0000
> @@ -22,6 +22,7 @@
> #include <err.h>
> #include <errno.h>
> #include <stdio.h>
> +#include <stdint.h>
> #include <string.h>
> #include <time.h>
> #include <unistd.h>
> @@ -31,9 +32,13 @@
> int
> do_timer(void)
> {
> - int kq, n;
> + static const int units[] = {
> + NOTE_SECONDS, NOTE_MSECONDS, NOTE_USECONDS, NOTE_NSECONDS
> + };
> struct kevent ev;
> - struct timespec ts;
> + struct timespec ts, start, end, now;
> + int64_t usecs;
> + int i, kq, n;
>
> ASS((kq = kqueue()) >= 0,
> warn("kqueue"));
> @@ -68,6 +73,125 @@ do_timer(void)
> n = kevent(kq, NULL, 0, &ev, 1, &ts);
> ASSX(n == 1);
>
> + /* Test with different time units */
> +
> + for (i = 0; i < sizeof(units) / sizeof(units[0]); i++) {
> + memset(&ev, 0, sizeof(ev));
> + ev.filter = EVFILT_TIMER;
> + ev.flags = EV_ADD | EV_ENABLE;
> + ev.fflags = units[i];
> + ev.data = 1;
> +
> + n = kevent(kq, &ev, 1, NULL, 0, NULL);
> + ASSX(n != -1);
> +
> + ts.tv_sec = 2; /* wait 2s for kqueue timeout */
> + ts.tv_nsec = 0;
> +
> + n = kevent(kq, NULL, 0, &ev, 1, &ts);
> + ASSX(n == 1);
> +
> + /* Delete timer to clear EV_CLEAR */
> +
> + memset(&ev, 0, sizeof(ev));
> + ev.filter = EVFILT_TIMER;
> + ev.flags = EV_DELETE;
> +
> + n = kevent(kq, &ev, 1, NULL, 0, NULL);
> + ASSX(n != -1);
> +
> + /* Test with NOTE_ABSTIME, deadline in the future */
> +
> + clock_gettime(CLOCK_MONOTONIC, &start);
> +
> + clock_gettime(CLOCK_REALTIME, &now);
> + memset(&ev, 0, sizeof(ev));
> + ev.filter = EVFILT_TIMER;
> + ev.flags = EV_ADD | EV_ENABLE;
> + ev.fflags = NOTE_ABSTIME | units[i];
> +
> + switch (units[i]) {
> + case NOTE_SECONDS:
> + ev.data = now.tv_sec + 1;
> + break;
> + case NOTE_MSECONDS:
> + ev.data = now.tv_sec * 1000 + now.tv_nsec / 1000000
> + + 100;
> + break;
> + case NOTE_USECONDS:
> + ev.data = now.tv_sec * 1000000 + now.tv_nsec / 1000
> + + 100 * 1000;
> + break;
> + case NOTE_NSECONDS:
> + ev.data = now.tv_sec * 1000000000 + now.tv_nsec
> + + 100 * 1000000;
> + break;
> + }
> +
> + n = kevent(kq, &ev, 1, NULL, 0, NULL);
> + ASSX(n != -1);
> +
> + ts.tv_sec = 2; /* wait 2s for kqueue timeout */
> + ts.tv_nsec = 0;
> +
> + n = kevent(kq, NULL, 0, &ev, 1, &ts);
> + ASSX(n == 1);
> +
> + clock_gettime(CLOCK_MONOTONIC, &end);
> + timespecsub(&end, &start, &ts);
> + usecs = ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
> + ASSX(usecs > 0);
> + ASSX(usecs < 1500000); /* allow wide margin */
> +
> + /* Test with NOTE_ABSTIME, deadline in the past. */
> +
> + clock_gettime(CLOCK_MONOTONIC, &start);
> +
> + memset(&ev, 0, sizeof(ev));
> + ev.filter = EVFILT_TIMER;
> + ev.flags = EV_ADD | EV_ENABLE;
> + ev.fflags = NOTE_ABSTIME | units[i];
> +
> + clock_gettime(CLOCK_REALTIME, &now);
> + switch (units[i]) {
> + case NOTE_SECONDS:
> + ev.data = now.tv_sec - 1;
> + break;
> + case NOTE_MSECONDS:
> + ev.data = now.tv_sec * 1000 + now.tv_nsec / 1000000
> + - 100;
> + break;
> + case NOTE_USECONDS:
> + ev.data = now.tv_sec * 1000000 + now.tv_nsec / 1000
> + - 100 * 1000;
> + break;
> + case NOTE_NSECONDS:
> + ev.data = now.tv_sec * 1000000000 + now.tv_nsec
> + - 100 * 1000000;
> + break;
> + }
> +
> + n = kevent(kq, &ev, 1, NULL, 0, NULL);
> + ASSX(n != -1);
> +
> + n = kevent(kq, NULL, 0, &ev, 1, &ts);
> + ASSX(n == 1);
> +
> + clock_gettime(CLOCK_MONOTONIC, &end);
> + timespecsub(&end, &start, &ts);
> + usecs = ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
> + ASSX(usecs > 0);
> + ASSX(usecs < 100000); /* allow wide margin */
> +
> + /* Test that the event remains active */
> +
> + ts.tv_sec = 2; /* wait 2s for kqueue timeout */
> + ts.tv_nsec = 0;
> +
> + n = kevent(kq, NULL, 0, &ev, 1, &ts);
> + ASSX(n == 1);
> + }
> +
> return (0);
> }
>
> @@ -96,6 +220,37 @@ do_invalid_timer(void)
> (long long)invalid_ts[i].tv_sec, invalid_ts[i].tv_nsec));
> }
>
> + /* Test invalid fflags */
> +
> + memset(&ev, 0, sizeof(ev));
> + ev.filter = EVFILT_TIMER;
> + ev.flags = EV_ADD | EV_ENABLE;
> + ev.fflags = ~NOTE_SECONDS;
> + ev.data = 1;
> +
> + n = kevent(kq, &ev, 1, NULL, 0, NULL);
> + ASSX(n == -1 && errno == EINVAL);
> +
> + memset(&ev, 0, sizeof(ev));
> + ev.filter = EVFILT_TIMER;
> + ev.flags = EV_ADD | EV_ENABLE;
> + ev.fflags = NOTE_MSECONDS;
> + ev.data = 500;
> +
> + n = kevent(kq, &ev, 1, NULL, 0, NULL);
> + ASSX(n == 0);
> +
> + /* Modify the existing timer */
> +
> + memset(&ev, 0, sizeof(ev));
> + ev.filter = EVFILT_TIMER;
> + ev.flags = EV_ADD | EV_ENABLE;
> + ev.fflags = ~NOTE_SECONDS;
> + ev.data = 1;
> +
> + n = kevent(kq, &ev, 1, NULL, 0, NULL);
> + ASSX(n == -1 && errno == EINVAL);
> +
> return (0);
> }
>
> Index: sys/kern/kern_event.c
> ===================================================================
> RCS file: src/sys/kern/kern_event.c,v
> retrieving revision 1.193
> diff -u -p -r1.193 kern_event.c
> --- sys/kern/kern_event.c 14 Aug 2022 01:58:27 -0000 1.193
> +++ sys/kern/kern_event.c 10 Sep 2022 13:01:38 -0000
> @@ -449,17 +449,61 @@ filt_proc(struct knote *kn, long hint)
> return (kn->kn_fflags != 0);
> }
>
> +#define NOTE_TIMER_UNITMASK \
> + (NOTE_SECONDS|NOTE_MSECONDS|NOTE_USECONDS|NOTE_NSECONDS)
> +
> +static int
> +filt_timervalidate(int sfflags, int64_t sdata, struct timespec *ts)
> +{
> + if (sfflags & ~(NOTE_TIMER_UNITMASK | NOTE_ABSTIME))
> + return (EINVAL);
> +
> + switch (sfflags & NOTE_TIMER_UNITMASK) {
> + case NOTE_SECONDS:
> + ts->tv_sec = sdata;
> + ts->tv_nsec = 0;
> + break;
> + case NOTE_MSECONDS:
> + ts->tv_sec = sdata / 1000;
> + ts->tv_nsec = (sdata % 1000) * 1000000;
> + break;
> + case NOTE_USECONDS:
> + ts->tv_sec = sdata / 1000000;
> + ts->tv_nsec = (sdata % 1000000) * 1000;
> + break;
> + case NOTE_NSECONDS:
> + ts->tv_sec = sdata / 1000000000;
> + ts->tv_nsec = sdata % 1000000000;
> + break;
> + default:
> + return (EINVAL);
> + }
> +
> + return (0);
> +}
> +
> static void
> -filt_timer_timeout_add(struct knote *kn)
> +filt_timeradd(struct knote *kn, struct timespec *ts)
> {
> - struct timeval tv;
> + struct timespec expiry, now;
> struct timeout *to = kn->kn_hook;
> int tticks;
>
> - tv.tv_sec = kn->kn_sdata / 1000;
> - tv.tv_usec = (kn->kn_sdata % 1000) * 1000;
> - tticks = tvtohz(&tv);
> - /* Remove extra tick from tvtohz() if timeout has fired before. */
> + if (kn->kn_sfflags & NOTE_ABSTIME) {
> + nanotime(&now);
> + if (timespeccmp(ts, &now, >)) {
> + timespecsub(ts, &now, &expiry);
> + /* XXX timeout_at_ts */
> + timeout_add(to, tstohz(&expiry));
> + } else {
> + /* Expire immediately. */
> + filt_timerexpire(kn);
> + }
> + return;
> + }
> +
> + tticks = tstohz(ts);
> + /* Remove extra tick from tstohz() if timeout has fired before. */
> if (timeout_triggered(to))
> tticks--;
I always wondered why one tick was removed, is one tick really that important?
And does a timeout firing only cost one tick?