Hi misc@

If I set a timer via kqueue, the timer is delivered 9–12ms late. This is
one a i7-13700K, but I see this happen consistency on other hardware.

Minimal reproducer:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/event.h>
#include <unistd.h>

int main(void) {
        const int timeout_ms = 40;
        printf("Running %dms timers.\n", timeout_ms);

        int kq = kqueue();
        if (kq == -1) {
                perror("kqueue");
                return 1;
        }

        struct kevent ev;
        EV_SET(&ev, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, timeout_ms, NULL);

        for (int i = 0; i < 10; i++) {
                struct timespec start, end;
                clock_gettime(CLOCK_MONOTONIC, &start);

                struct kevent changed;
                if (kevent(kq, &ev, 1, &changed, 1, NULL) == -1) {
                        perror("kevent");
                        close(kq);
                        return 1;
                }

                clock_gettime(CLOCK_MONOTONIC, &end);

                long duration_ms = (end.tv_sec - start.tv_sec) * 1000 + 
(end.tv_nsec - start.tv_nsec) / 1000000;
                printf("duration: %ldms\n", duration_ms);
        }

        close(kq);
        return 0;
}

Sample output of the above:

    > cc -o /tmp/minimal_reproducer/timer /tmp/minimal_reproducer/src/main.c && 
/tmp/minimal_reproducer/timer
    Running 40ms timers.
    duration: 42ms
    duration: 49ms
    duration: 49ms
    duration: 49ms
    duration: 50ms
    duration: 49ms
    duration: 50ms
    duration: 49ms
    duration: 50ms
    duration: 49ms

I see similar results when using gettimeofday instead of clock_gettime.
Using select() instead of kqueue also yields the same results.

The same program on FreeBSD prints "duration: 40ms" 100% of the time. I
haven't tried other BSD variants.

After looking at src, I get the impression that timers are tick-based at
100Hz (based on sys/sys/kernel.h and sys/conf/param.c, but I might be
looking at the wrong thing). That's a  granularity of 10ms, and most
likely the explanation of this issue.

If I'm understating everything correctly, the kernel's internal
intrclock interface enabled setting timers with nanosecond precision,
and sets a hardware timer for the next queued timer. Can't userspace
timers be routed to that same plumbing and avoid the 10ms overhead?

-- 
Hugo

Reply via email to