Index: sys/kern/kern_event.c =================================================================== --- sys/kern/kern_event.c +++ sys/kern/kern_event.c @@ -162,6 +162,8 @@ static void filt_timerexpire(void *knx); static int filt_timerattach(struct knote *kn); static void filt_timerdetach(struct knote *kn); +static void filt_timerstart(struct knote *kn, sbintime_t to); +static int filt_timervalidate(struct knote *kn, sbintime_t *to); static int filt_timer(struct knote *kn, long hint); static int filt_userattach(struct knote *kn); static void filt_userdetach(struct knote *kn); @@ -673,6 +675,8 @@ struct callout c; sbintime_t next; /* next timer event fires at */ sbintime_t to; /* precalculated timer period, 0 for abs */ + intptr_t sdata; /* Saved data */ + int sfflags; /* Saved fflags */ }; static void @@ -699,12 +703,10 @@ * data contains amount of time to sleep */ static int -filt_timerattach(struct knote *kn) +filt_timervalidate(struct knote *kn, sbintime_t *to) { - struct kq_timer_cb_data *kc; struct bintime bt; - sbintime_t to, sbt; - unsigned int ncallouts; + sbintime_t sbt; if (kn->kn_sdata < 0) return (EINVAL); @@ -714,14 +716,28 @@ if ((kn->kn_sfflags & ~(NOTE_TIMER_PRECMASK | NOTE_ABSTIME)) != 0) return (EINVAL); - to = timer2sbintime(kn->kn_sdata, kn->kn_sfflags); + *to = timer2sbintime(kn->kn_sdata, kn->kn_sfflags); if ((kn->kn_sfflags & NOTE_ABSTIME) != 0) { getboottimebin(&bt); sbt = bttosbt(bt); - to -= sbt; + *to -= sbt; } - if (to < 0) + if (*to < 0) return (EINVAL); + return (0); +} + +static int +filt_timerattach(struct knote *kn) +{ + struct kq_timer_cb_data *kc; + sbintime_t to; + unsigned int ncallouts; + int error; + + error = filt_timervalidate(kn, &to); + if (error != 0) + return (error); do { ncallouts = kq_ncallouts; @@ -734,6 +750,17 @@ kn->kn_status &= ~KN_DETACHED; /* knlist_add clears it */ kn->kn_ptr.p_v = kc = malloc(sizeof(*kc), M_KQUEUE, M_WAITOK); callout_init(&kc->c, 1); + filt_timerstart(kn, to); + + return (0); +} + +static void +filt_timerstart(struct knote *kn, sbintime_t to) +{ + struct kq_timer_cb_data *kc; + + kc = kn->kn_ptr.p_v; if ((kn->kn_sfflags & NOTE_ABSTIME) != 0) { kc->next = to; kc->to = 0; @@ -741,10 +768,10 @@ kc->next = to + sbinuptime(); kc->to = to; } + kc->sdata = kn->kn_sdata; + kc->sfflags = kn->kn_sfflags; callout_reset_sbt_on(&kc->c, kc->next, 0, filt_timerexpire, kn, PCPU_GET(cpuid), C_ABSOLUTE); - - return (0); } static void @@ -764,6 +791,21 @@ static int filt_timer(struct knote *kn, long hint) { + struct kq_timer_cb_data *kc; + sbintime_t to; + int error; + + kc = kn->kn_ptr.p_v; + /* Handle re-added timers that update data/fflags */ + if ((kc->sdata != kn->kn_sdata || kc->sfflags != kn->kn_sfflags) && + ((kn->kn_flags & EV_ONESHOT) == 0 || kn->kn_data == 0)) { + error = filt_timervalidate(kn, &to); + if (error != 0) { + kn->kn_flags |= EV_ERROR; + kn->kn_data = error; + } else + filt_timerstart(kn, to); + } return (kn->kn_data != 0); } Index: tests/sys/kqueue/libkqueue/timer.c =================================================================== --- tests/sys/kqueue/libkqueue/timer.c +++ tests/sys/kqueue/libkqueue/timer.c @@ -19,8 +19,38 @@ #include "common.h" #include +#define MILLION 1000000UL +#define THOUSAND 1000 +#define SEC_TO_US(t) (t * MILLION) /* Convert seconds to microseconds. */ +#define MS_TO_US(t) (t * THOUSAND) /* Convert milliseconds to microseconds. */ +#define US_TO_NS(t) (t * THOUSAND) /* Convert microseconds to nanoseconds. */ + int kqfd; +/* Get the current time with microsecond precision. Used for + * sub-second timing to make some timer tests run faster. + */ +static uint64_t +now(void) +{ + + struct timeval tv; + gettimeofday(&tv, NULL); + return SEC_TO_US(tv.tv_sec) + tv.tv_usec; +} + +/* Sleep for a given number of milliseconds. + */ +void +mssleep(int t) +{ + struct timespec stime = { + .tv_sec = 0, + .tv_nsec = US_TO_NS(MS_TO_US(t)), + }; + nanosleep(&stime, NULL); +} + void test_kevent_timer_add(void) { @@ -198,6 +228,96 @@ success(); } +static void +test_update(void) +{ + const char *test_id = "kevent(EVFILT_TIMER (UPDATE), EV_ADD | EV_ONESHOT)"; + struct kevent kev; + uint64_t elapsed; + uint64_t start; + + test_begin(test_id); + + test_no_kevents(); + + /* First set the timer to 1 second */ + EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, + NOTE_USECONDS, SEC_TO_US(1), NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Now reduce the timer to 1 ms */ + EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, + NOTE_USECONDS, MS_TO_US(1), NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Wait for the event */ + start = now(); + kev.flags |= EV_CLEAR; + kev.fflags &= ~NOTE_USECONDS; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + elapsed = now() - start; + /* Check that the timer expired after at least 1 ms, but less than + * 2 ms. This check is to make sure that the original 1 second + * timeout was not used. + */ + if (elapsed < MS_TO_US(1) || elapsed > MS_TO_US(2)) + err(1, "unexpected timer expiration: %lu", elapsed); + + success(); +} + +static void +test_update_expired(void) +{ + const char *test_id = "kevent(EVFILT_TIMER (UPDATE EXP), EV_ADD | EV_ONESHOT)"; + struct kevent kev; + uint64_t elapsed; + uint64_t start; + + test_begin(test_id); + + test_no_kevents(); + + /* Set the timer to 1ms */ + EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, + NOTE_USECONDS, MS_TO_US(1), NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Wait for 2 ms to give the timer plenty of time to expire. */ + mssleep(2); + + /* Now re-add the timer */ + EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, + NOTE_USECONDS, MS_TO_US(1), NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Wait for the event */ + start = now(); + kev.flags |= EV_CLEAR; + kev.fflags &= ~NOTE_USECONDS; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + /* The event should have been received in much less than 1 ms + * since it was allowed to expire and re-adding it should have no + * effect. + */ + elapsed = now() - start; + if (elapsed >= MS_TO_US(1)) + err(1, "unexpected timer expiration: %lu", elapsed); + + /* Make sure the re-added timer does not fire. */ + mssleep(2); + test_no_kevents(); + + success(); +} + void test_evfilt_timer() { @@ -208,6 +328,8 @@ test_oneshot(); test_periodic(); test_abstime(); + test_update(); + test_update_expired(); disable_and_enable(); close(kqfd); }