/* * Copyright (c) 2009 Mark Heily * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $FreeBSD$ */ #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. */ /* 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); /* Promote potentially 32-bit time_t to uint64_t before conversion. */ return SEC_TO_US((uint64_t)tv.tv_sec) + tv.tv_usec; } /* Sleep for a given number of milliseconds. The timeout is assumed to * be less than 1 second. */ static 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. */ static void ussleep(int t) { struct timespec stime = { .tv_sec = 0, .tv_nsec = US_TO_NS(t), }; nanosleep(&stime, NULL); } static void test_kevent_timer_add(void) { const char *test_id = "kevent(EVFILT_TIMER, EV_ADD)"; struct kevent kev; test_begin(test_id); EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL); if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) err(1, "%s", test_id); success(); } static void test_kevent_timer_del(void) { const char *test_id = "kevent(EVFILT_TIMER, EV_DELETE)"; struct kevent kev; test_begin(test_id); EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) err(1, "%s", test_id); test_no_kevents(); success(); } static void test_kevent_timer_get(void) { const char *test_id = "kevent(EVFILT_TIMER, wait)"; struct kevent kev; test_begin(test_id); EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL); if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) err(1, "%s", test_id); kev.flags |= EV_CLEAR; kev.data = 1; kevent_cmp(&kev, kevent_get(kqfd)); EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) err(1, "%s", test_id); success(); } static void test_oneshot(void) { const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT)"; struct kevent kev; test_begin(test_id); test_no_kevents(); EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 500,NULL); if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) err(1, "%s", test_id); /* Retrieve the event */ kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT; kev.data = 1; kevent_cmp(&kev, kevent_get(kqfd)); /* Check if the event occurs again */ sleep(3); test_no_kevents(); success(); } static void test_periodic(void) { const char *test_id = "kevent(EVFILT_TIMER, periodic)"; struct kevent kev; test_begin(test_id); test_no_kevents(); EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000,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)); /* Delete the event */ kev.flags = EV_DELETE; if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) err(1, "%s", test_id); success(); } static void disable_and_enable(void) { const char *test_id = "kevent(EVFILT_TIMER, EV_DISABLE and EV_ENABLE)"; struct kevent kev; test_begin(test_id); test_no_kevents(); /* Add the watch and immediately disable it */ EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 2000,NULL); if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) err(1, "%s", test_id); kev.flags = EV_DISABLE; if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) err(1, "%s", test_id); test_no_kevents(); /* Re-enable and check again */ kev.flags = EV_ENABLE; if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) err(1, "%s", test_id); kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT; kev.data = 1; kevent_cmp(&kev, kevent_get(kqfd)); success(); } static void test_abstime(void) { const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT, NOTE_ABSTIME)"; struct kevent kev; uint64_t end, start, stop; const int timeout_sec = 3; test_begin(test_id); test_no_kevents(); start = now(); end = start + SEC_TO_US(timeout_sec); EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, NOTE_ABSTIME | NOTE_USECONDS, end, NULL); if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) err(1, "%s", test_id); /* Retrieve the event */ kev.flags = EV_ADD | EV_ONESHOT; kev.data = 1; kev.fflags = 0; kevent_cmp(&kev, kevent_get(kqfd)); stop = now(); if (stop < end) err(1, "too early %jd %jd", (intmax_t)stop, (intmax_t)end); /* Check if the event occurs again */ sleep(3); test_no_kevents(); success(); } static void test_update(void) { const char *test_id = "kevent(EVFILT_TIMER (UPDATE), EV_ADD | EV_ONESHOT)"; struct kevent kev; long 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), (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; uint64_t 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; 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 */ 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; uint64_t start, 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; uint64_t start, 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(void) { kqfd = kqueue(); 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); }