Index: head/lib/libc/sys/kqueue.2 =================================================================== --- head/lib/libc/sys/kqueue.2 +++ head/lib/libc/sys/kqueue.2 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd June 22, 2017 +.Dd July 27, 2018 .Dt KQUEUE 2 .Os .Sh NAME @@ -154,7 +154,7 @@ u_int fflags; /* filter flag value */ int64_t data; /* filter data value */ void *udata; /* opaque user data identifier */ - uint64_t ext[4]; /* extentions */ + uint64_t ext[4]; /* extensions */ }; .Ed .Pp @@ -355,8 +355,8 @@ .Fn kevent directly but are registered via the .Va aio_sigevent -member of an asychronous I/O request when it is scheduled via an asychronous I/O -system call such as +member of an asynchronous I/O request when it is scheduled via an +asynchronous I/O system call such as .Fn aio_read . The filter returns under the same conditions as .Fn aio_error . @@ -501,7 +501,7 @@ .Dv SIG_IGN , except for the .Dv SIGCHLD -signal, which, if ignored, won't be recorded by the filter. +signal, which, if ignored, will not be recorded by the filter. Event notification happens after normal signal delivery processing. .Va data @@ -558,6 +558,13 @@ .Va fflags contains the events which triggered the filter. .Pp +If an existing timer is re-added, the existing timer will be +effectively canceled (throwing away any undelivered record of previous +timer expiration) and re-started using the new parameters contained in +.Va data +and +.Va fflags . +.Pp There is a system wide limit on the number of timers which is controlled by the .Va kern.kq_calloutmax @@ -604,9 +611,9 @@ .Sh CANCELLATION BEHAVIOUR If .Fa nevents -is non-zero, i.e. the function is potentially blocking, the call +is non-zero, i.e., the function is potentially blocking, the call is a cancellation point. -Otherwise, i.e. if +Otherwise, i.e., if .Fa nevents is zero, the call is not cancellable. Cancellation can only occur before any changes are made to the kqueue, @@ -782,7 +789,7 @@ value is limited to 24 hours; longer timeouts will be silently reinterpreted as 24 hours. .Pp -In versions older than +In versions older than .Fx 12.0 , .In sys/event.h failed to parse without including Index: head/sys/kern/kern_event.c =================================================================== --- head/sys/kern/kern_event.c +++ head/sys/kern/kern_event.c @@ -162,6 +162,10 @@ 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 void filt_timertouch(struct knote *kn, struct kevent *kev, + u_long type); +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); @@ -190,6 +194,7 @@ .f_attach = filt_timerattach, .f_detach = filt_timerdetach, .f_event = filt_timer, + .f_touch = filt_timertouch, }; static struct filterops user_filtops = { .f_attach = filt_userattach, @@ -699,30 +704,45 @@ * 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); if (kn->kn_sdata == 0 && (kn->kn_flags & EV_ONESHOT) == 0) kn->kn_sdata = 1; - /* Only precision unit are supported in flags so far */ + /* + * The only fflags values supported are the timer unit + * (precision) and the absolute time indicator. + */ 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; if (ncallouts >= kq_calloutmax) @@ -734,6 +754,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; @@ -743,8 +774,6 @@ } callout_reset_sbt_on(&kc->c, kc->next, 0, filt_timerexpire, kn, PCPU_GET(cpuid), C_ABSOLUTE); - - return (0); } static void @@ -759,6 +788,73 @@ old = atomic_fetchadd_int(&kq_ncallouts, -1); KASSERT(old > 0, ("Number of callouts cannot become negative")); kn->kn_status |= KN_DETACHED; /* knlist_remove sets it */ +} + +static void +filt_timertouch(struct knote *kn, struct kevent *kev, u_long type) +{ + struct kq_timer_cb_data *kc; + struct kqueue *kq; + sbintime_t to; + int error; + + switch (type) { + case EVENT_REGISTER: + /* Handle re-added timers that update data/fflags */ + if (kev->flags & EV_ADD) { + kc = kn->kn_ptr.p_v; + + /* Drain any existing callout. */ + callout_drain(&kc->c); + + /* Throw away any existing undelivered record + * of the timer expiration. This is done under + * the presumption that if a process is + * re-adding this timer with new parameters, + * it is no longer interested in what may have + * happened under the old parameters. If it is + * interested, it can wait for the expiration, + * delete the old timer definition, and then + * add the new one. + * + * This has to be done while the kq is locked: + * - if enqueued, dequeue + * - make it no longer active + * - clear the count of expiration events + */ + kq = kn->kn_kq; + KQ_LOCK(kq); + if (kn->kn_status & KN_QUEUED) + knote_dequeue(kn); + + kn->kn_status &= ~KN_ACTIVE; + kn->kn_data = 0; + KQ_UNLOCK(kq); + + /* Reschedule timer based on new data/fflags */ + kn->kn_sfflags = kev->fflags; + kn->kn_sdata = kev->data; + error = filt_timervalidate(kn, &to); + if (error != 0) { + kn->kn_flags |= EV_ERROR; + kn->kn_data = error; + } else + filt_timerstart(kn, to); + } + break; + + case EVENT_PROCESS: + *kev = kn->kn_kevent; + if (kn->kn_flags & EV_CLEAR) { + kn->kn_data = 0; + kn->kn_fflags = 0; + } + break; + + default: + panic("filt_timertouch() - invalid type (%ld)", type); + break; + } } static int Index: head/tests/sys/kqueue/libkqueue/common.h =================================================================== --- head/tests/sys/kqueue/libkqueue/common.h +++ head/tests/sys/kqueue/libkqueue/common.h @@ -19,6 +19,7 @@ #ifndef _COMMON_H #define _COMMON_H +#include "config.h" /* Needed for HAVE_* defines */ #if HAVE_ERR_H # include @@ -39,8 +40,6 @@ #include -#include "config.h" - extern char *cur_test_id; int vnode_fd; @@ -72,6 +71,7 @@ /* Checks if any events are pending, which is an error. */ extern void test_no_kevents(void); +extern void test_no_kevents_quietly(void); extern void test_begin(const char *); extern void success(void); Index: head/tests/sys/kqueue/libkqueue/main.c =================================================================== --- head/tests/sys/kqueue/libkqueue/main.c +++ head/tests/sys/kqueue/libkqueue/main.c @@ -52,6 +52,25 @@ } } +/* Checks if any events are pending, which is an error. Do not print + * out anything unless events are found. +*/ +void +test_no_kevents_quietly(void) +{ + int nfds; + struct timespec timeo; + struct kevent kev; + + memset(&timeo, 0, sizeof(timeo)); + nfds = kevent(kqfd, NULL, 0, &kev, 1, &timeo); + if (nfds != 0) { + puts("\nUnexpected event:"); + puts(kevent_to_str(&kev)); + errx(1, "%d event(s) pending, but none expected:", nfds); + } +} + /* Retrieve a single kevent */ struct kevent * kevent_get(int kqfd) Index: head/tests/sys/kqueue/libkqueue/timer.c =================================================================== --- head/tests/sys/kqueue/libkqueue/timer.c +++ head/tests/sys/kqueue/libkqueue/timer.c @@ -19,9 +19,59 @@ #include "common.h" #include +#define MILLION 1000000 +#define THOUSAND 1000 +#define SEC_TO_MS(t) ((t) * THOUSAND) /* Convert seconds to milliseconds. */ +#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 long +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. The timeout is assumed to + * be less than 1 second. + */ void +mssleep(int t) +{ + + struct timespec stime = { + .tv_sec = 0, + .tv_nsec = US_TO_NS(MS_TO_US(t)), + }; + + nanosleep(&stime, NULL); +} + +/* Sleep for a given number of microseconds. The timeout is assumed to + * be less than 1 second. + */ +void +ussleep(int t) +{ + + struct timespec stime = { + .tv_sec = 0, + .tv_nsec = US_TO_NS(t), + }; + + nanosleep(&stime, NULL); +} + +void test_kevent_timer_add(void) { const char *test_id = "kevent(EVFILT_TIMER, EV_ADD)"; @@ -189,7 +239,7 @@ kev.fflags = 0; kevent_cmp(&kev, kevent_get(kqfd)); if (time(NULL) < when + timeout) - err(1, "too early %jd %jd", time(), when + timeout); + err(1, "too early %jd %jd", time(NULL), when + timeout); /* Check if the event occurs again */ sleep(3); @@ -198,16 +248,283 @@ success(); } +static void +test_update(void) +{ + const char *test_id = "kevent(EVFILT_TIMER (UPDATE), EV_ADD | EV_ONESHOT)"; + struct kevent kev; + long elapsed; + long 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), (void *)1); + start = now(); + 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), (void *)2); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Wait for the event */ + 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 + * 1 second. This check is to make sure that the original 1 second + * timeout was not used. + */ + printf("timer expired after %ld us\n", elapsed); + if (elapsed < MS_TO_US(1)) + errx(1, "early timer expiration: %ld us", elapsed); + if (elapsed > SEC_TO_US(1)) + errx(1, "late timer expiration: %ld us", elapsed); + + success(); +} + +static void +test_update_equal(void) +{ + const char *test_id = "kevent(EVFILT_TIMER (UPDATE=), EV_ADD | EV_ONESHOT)"; + struct kevent kev; + long elapsed; + long start; + + test_begin(test_id); + + test_no_kevents(); + + /* First set 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); + + /* Sleep for a significant fraction of the timeout. */ + ussleep(600); + + /* Now re-add the timer with the same parameters */ + start = now(); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Wait for the event */ + 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. This check is + * to make sure that the timer re-started and that the event is + * not from the original add of the timer. + */ + printf("timer expired after %ld us\n", elapsed); + if (elapsed < MS_TO_US(1)) + errx(1, "early timer expiration: %ld us", elapsed); + + success(); +} + +static void +test_update_expired(void) +{ + const char *test_id = "kevent(EVFILT_TIMER (UPDATE EXP), EV_ADD | EV_ONESHOT)"; + struct kevent kev; + long elapsed; + long 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 */ + start = now(); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Wait for the event */ + 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. This check + * is to make sure that the timer re-started and that the event is + * not from the original add (and expiration) of the timer. + */ + printf("timer expired after %ld us\n", elapsed); + if (elapsed < MS_TO_US(1)) + errx(1, "early timer expiration: %ld us", elapsed); + + /* Make sure the re-added timer does not fire. In other words, + * test that the event received above was the only event from the + * add and re-add of the timer. + */ + mssleep(2); + test_no_kevents(); + + success(); +} + +static void +test_update_periodic(void) +{ + const char *test_id = "kevent(EVFILT_TIMER (UPDATE), periodic)"; + struct kevent kev; + long elapsed; + long start; + long stop; + + test_begin(test_id); + + test_no_kevents(); + + EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, SEC_TO_MS(1), NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Retrieve the event */ + kev.flags = EV_ADD | EV_CLEAR; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + /* Check if the event occurs again */ + sleep(1); + kevent_cmp(&kev, kevent_get(kqfd)); + + /* Re-add with new timeout. */ + EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, SEC_TO_MS(2), NULL); + start = now(); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Retrieve the event */ + kev.flags = EV_ADD | EV_CLEAR; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + stop = now(); + elapsed = stop - start; + + /* Check that the timer expired after at least 2 ms. + */ + printf("timer expired after %ld us\n", elapsed); + if (elapsed < MS_TO_US(2)) + errx(1, "early timer expiration: %ld us", elapsed); + + /* Delete the event */ + kev.flags = EV_DELETE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +static void +test_update_timing(void) +{ +#define MIN_SLEEP 500 +#define MAX_SLEEP 1500 + const char *test_id = "kevent(EVFILT_TIMER (UPDATE TIMING), EV_ADD | EV_ONESHOT)"; + struct kevent kev; + int iteration; + int sleeptime; + long elapsed; + long start; + long stop; + + test_begin(test_id); + + test_no_kevents(); + + /* Re-try the update tests with a variety of delays between the + * original timer activation and the update of the timer. The goal + * is to show that in all cases the only timer event that is + * received is from the update and not the original timer add. + */ + for (sleeptime = MIN_SLEEP, iteration = 1; + sleeptime < MAX_SLEEP; + ++sleeptime, ++iteration) { + + /* First set 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); + + /* Delay; the delay ranges from less than to greater than the + * timer period. + */ + ussleep(sleeptime); + + /* Now re-add the timer with the same parameters */ + start = now(); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Wait for the event */ + kev.flags |= EV_CLEAR; + kev.fflags &= ~NOTE_USECONDS; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + stop = now(); + elapsed = stop - start; + + /* Check that the timer expired after at least 1 ms. This + * check is to make sure that the timer re-started and that + * the event is not from the original add of the timer. + */ + if (elapsed < MS_TO_US(1)) + errx(1, "early timer expiration: %ld us", elapsed); + + /* Make sure the re-added timer does not fire. In other words, + * test that the event received above was the only event from + * the add and re-add of the timer. + */ + mssleep(2); + test_no_kevents_quietly(); + } + + success(); +} + void test_evfilt_timer() { kqfd = kqueue(); - test_kevent_timer_add(); - test_kevent_timer_del(); - test_kevent_timer_get(); - test_oneshot(); - test_periodic(); - test_abstime(); - disable_and_enable(); + test_kevent_timer_add(); + test_kevent_timer_del(); + test_kevent_timer_get(); + test_oneshot(); + test_periodic(); + test_abstime(); + test_update(); + test_update_equal(); + test_update_expired(); + test_update_timing(); + test_update_periodic(); + disable_and_enable(); close(kqfd); }