Path: blob/main/tests/sys/kqueue/libkqueue/timer.c
39566 views
/*1* Copyright (c) 2009 Mark Heily <[email protected]>2*3* Permission to use, copy, modify, and distribute this software for any4* purpose with or without fee is hereby granted, provided that the above5* copyright notice and this permission notice appear in all copies.6*7* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES8* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF9* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR10* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES11* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN12* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF13* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.14*/1516#include "common.h"17#include <sys/time.h>1819#define MILLION 100000020#define THOUSAND 100021#define SEC_TO_MS(t) ((t) * THOUSAND) /* Convert seconds to milliseconds. */22#define SEC_TO_US(t) ((t) * MILLION) /* Convert seconds to microseconds. */23#define MS_TO_US(t) ((t) * THOUSAND) /* Convert milliseconds to microseconds. */24#define US_TO_NS(t) ((t) * THOUSAND) /* Convert microseconds to nanoseconds. */252627/* Get the current time with microsecond precision. Used for28* sub-second timing to make some timer tests run faster.29*/30static uint64_t31now(void)32{33struct timeval tv;3435gettimeofday(&tv, NULL);36/* Promote potentially 32-bit time_t to uint64_t before conversion. */37return SEC_TO_US((uint64_t)tv.tv_sec) + tv.tv_usec;38}3940/* Sleep for a given number of milliseconds. The timeout is assumed to41* be less than 1 second.42*/43static void44mssleep(int t)45{46struct timespec stime = {47.tv_sec = 0,48.tv_nsec = US_TO_NS(MS_TO_US(t)),49};5051nanosleep(&stime, NULL);52}5354/* Sleep for a given number of microseconds. The timeout is assumed to55* be less than 1 second.56*/57static void58ussleep(int t)59{60struct timespec stime = {61.tv_sec = 0,62.tv_nsec = US_TO_NS(t),63};6465nanosleep(&stime, NULL);66}6768static void69test_kevent_timer_add(void)70{71const char *test_id = "kevent(EVFILT_TIMER, EV_ADD)";72struct kevent kev;7374test_begin(test_id);7576EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);77if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)78err(1, "%s", test_id);7980success();81}8283static void84test_kevent_timer_del(void)85{86const char *test_id = "kevent(EVFILT_TIMER, EV_DELETE)";87struct kevent kev;8889test_begin(test_id);9091EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);92if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)93err(1, "%s", test_id);9495test_no_kevents();9697success();98}99100static void101test_kevent_timer_get(void)102{103const char *test_id = "kevent(EVFILT_TIMER, wait)";104struct kevent kev;105106test_begin(test_id);107108EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);109if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)110err(1, "%s", test_id);111112kev.flags |= EV_CLEAR;113kev.data = 1;114kevent_cmp(&kev, kevent_get(kqfd));115116EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);117if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)118err(1, "%s", test_id);119120success();121}122123static void124test_oneshot(void)125{126const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT)";127struct kevent kev;128129test_begin(test_id);130131test_no_kevents();132133EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 500,NULL);134if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)135err(1, "%s", test_id);136137/* Retrieve the event */138kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;139kev.data = 1;140kevent_cmp(&kev, kevent_get(kqfd));141142/* Check if the event occurs again */143sleep(3);144test_no_kevents();145146147success();148}149150static void151test_periodic(void)152{153const char *test_id = "kevent(EVFILT_TIMER, periodic)";154struct kevent kev;155156test_begin(test_id);157158test_no_kevents();159160EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000,NULL);161if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)162err(1, "%s", test_id);163164/* Retrieve the event */165kev.flags = EV_ADD | EV_CLEAR;166kev.data = 1;167kevent_cmp(&kev, kevent_get(kqfd));168169/* Check if the event occurs again */170sleep(1);171kevent_cmp(&kev, kevent_get(kqfd));172173/* Delete the event */174kev.flags = EV_DELETE;175if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)176err(1, "%s", test_id);177178success();179}180181static void182test_periodic_modify(void)183{184const char *test_id = "kevent(EVFILT_TIMER, periodic_modify)";185struct kevent kev;186187test_begin(test_id);188189test_no_kevents();190191EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);192if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)193err(1, "%s", test_id);194195/* Retrieve the event */196kev.flags = EV_ADD | EV_CLEAR;197kev.data = 1;198kevent_cmp(&kev, kevent_get(kqfd));199200/* Check if the event occurs again */201EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 495, NULL);202if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)203err(1, "%s", test_id);204205kev.flags = EV_ADD | EV_CLEAR;206sleep(1);207kev.data = 2; /* Should have fired twice */208209kevent_cmp(&kev, kevent_get(kqfd));210211/* Delete the event */212kev.flags = EV_DELETE;213if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)214err(1, "%s", test_id);215216success();217}218219#if WITH_NATIVE_KQUEUE_BUGS220static void221test_periodic_to_oneshot(void)222{223const char *test_id = "kevent(EVFILT_TIMER, period_to_oneshot)";224struct kevent kev;225226test_begin(test_id);227228test_no_kevents();229230EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);231if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)232err(1, "%s", test_id);233234/* Retrieve the event */235kev.flags = EV_ADD | EV_CLEAR;236kev.data = 1;237kevent_cmp(&kev, kevent_get(kqfd));238239/* Check if the event occurs again */240sleep(1);241kevent_cmp(&kev, kevent_get(kqfd));242243/* Switch to oneshot */244EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 500, NULL);245if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)246err(1, "%s", test_id);247kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;248249sleep(1);250kev.data = 1; /* Should have fired once */251252kevent_cmp(&kev, kevent_get(kqfd));253254success();255}256#endif257258static void259test_disable_and_enable(void)260{261const char *test_id = "kevent(EVFILT_TIMER, EV_DISABLE and EV_ENABLE)";262struct kevent kev;263264test_begin(test_id);265266test_no_kevents();267268/* Add the watch and immediately disable it */269EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 2000,NULL);270if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)271err(1, "%s", test_id);272kev.flags = EV_DISABLE;273if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)274err(1, "%s", test_id);275test_no_kevents();276277/* Re-enable and check again */278kev.flags = EV_ENABLE;279if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)280err(1, "%s", test_id);281282kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;283kev.data = 1;284kevent_cmp(&kev, kevent_get(kqfd));285286success();287}288289static void290test_abstime(void)291{292const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT, NOTE_ABSTIME)";293struct kevent kev;294uint64_t end, start, stop;295const int timeout_sec = 3;296297test_begin(test_id);298299test_no_kevents();300301start = now();302end = start + SEC_TO_US(timeout_sec);303EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,304NOTE_ABSTIME | NOTE_USECONDS, end, NULL);305if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)306err(1, "%s", test_id);307308/* Retrieve the event */309kev.flags = EV_ADD | EV_ONESHOT;310kev.data = 1;311kev.fflags = 0;312kevent_cmp(&kev, kevent_get(kqfd));313314stop = now();315if (stop < end)316err(1, "too early %jd %jd", (intmax_t)stop, (intmax_t)end);317/* Check if the event occurs again */318sleep(3);319test_no_kevents();320321success();322}323324static void325test_abstime_epoch(void)326{327const char *test_id = "kevent(EVFILT_TIMER (EPOCH), NOTE_ABSTIME)";328struct kevent kev;329330test_begin(test_id);331332test_no_kevents();333334EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, NOTE_ABSTIME, 0,335NULL);336if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)337err(1, "%s", test_id);338339/* Retrieve the event */340kev.flags = EV_ADD;341kev.data = 1;342kev.fflags = 0;343kevent_cmp(&kev, kevent_get(kqfd));344345/* Delete the event */346kev.flags = EV_DELETE;347if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)348err(1, "%s", test_id);349350success();351}352353static void354test_abstime_preboot(void)355{356const char *test_id = "kevent(EVFILT_TIMER (PREBOOT), EV_ONESHOT, NOTE_ABSTIME)";357struct kevent kev;358struct timespec btp;359uint64_t end, start, stop;360361test_begin(test_id);362363test_no_kevents();364365/*366* We'll expire it at just before system boot (roughly) with the hope that367* we'll get an ~immediate expiration, just as we do for any value specified368* between system boot and now.369*/370start = now();371if (clock_gettime(CLOCK_BOOTTIME, &btp) != 0)372err(1, "%s", test_id);373374end = start - SEC_TO_US(btp.tv_sec + 1);375EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,376NOTE_ABSTIME | NOTE_USECONDS, end, NULL);377if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)378err(1, "%s", test_id);379380/* Retrieve the event */381kev.flags = EV_ADD | EV_ONESHOT;382kev.data = 1;383kev.fflags = 0;384kevent_cmp(&kev, kevent_get(kqfd));385386stop = now();387if (stop < end)388err(1, "too early %jd %jd", (intmax_t)stop, (intmax_t)end);389/* Check if the event occurs again */390sleep(3);391test_no_kevents();392393success();394}395396static void397test_abstime_postboot(void)398{399const char *test_id = "kevent(EVFILT_TIMER (POSTBOOT), EV_ONESHOT, NOTE_ABSTIME)";400struct kevent kev;401uint64_t end, start, stop;402const int timeout_sec = 1;403404test_begin(test_id);405406test_no_kevents();407408/*409* Set a timer for 1 second ago, it should fire immediately rather than410* being rejected.411*/412start = now();413end = start - SEC_TO_US(timeout_sec);414EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,415NOTE_ABSTIME | NOTE_USECONDS, end, NULL);416if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)417err(1, "%s", test_id);418419/* Retrieve the event */420kev.flags = EV_ADD | EV_ONESHOT;421kev.data = 1;422kev.fflags = 0;423kevent_cmp(&kev, kevent_get(kqfd));424425stop = now();426if (stop < end)427err(1, "too early %jd %jd", (intmax_t)stop, (intmax_t)end);428/* Check if the event occurs again */429sleep(3);430test_no_kevents();431432success();433}434435static void436test_update(void)437{438const char *test_id = "kevent(EVFILT_TIMER (UPDATE), EV_ADD | EV_ONESHOT)";439struct kevent kev;440long elapsed;441uint64_t start;442443test_begin(test_id);444445test_no_kevents();446447/* First set the timer to 1 second */448EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,449NOTE_USECONDS, SEC_TO_US(1), (void *)1);450start = now();451if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)452err(1, "%s", test_id);453454/* Now reduce the timer to 1 ms */455EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,456NOTE_USECONDS, MS_TO_US(1), (void *)2);457if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)458err(1, "%s", test_id);459460/* Wait for the event */461kev.flags |= EV_CLEAR;462kev.fflags &= ~NOTE_USECONDS;463kev.data = 1;464kevent_cmp(&kev, kevent_get(kqfd));465elapsed = now() - start;466467/* Check that the timer expired after at least 1 ms, but less than468* 1 second. This check is to make sure that the original 1 second469* timeout was not used.470*/471printf("timer expired after %ld us\n", elapsed);472if (elapsed < MS_TO_US(1))473errx(1, "early timer expiration: %ld us", elapsed);474if (elapsed > SEC_TO_US(1))475errx(1, "late timer expiration: %ld us", elapsed);476477success();478}479480static void481test_update_equal(void)482{483const char *test_id = "kevent(EVFILT_TIMER (UPDATE=), EV_ADD | EV_ONESHOT)";484struct kevent kev;485long elapsed;486uint64_t start;487488test_begin(test_id);489490test_no_kevents();491492/* First set the timer to 1 ms */493EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,494NOTE_USECONDS, MS_TO_US(1), NULL);495if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)496err(1, "%s", test_id);497498/* Sleep for a significant fraction of the timeout. */499ussleep(600);500501/* Now re-add the timer with the same parameters */502start = now();503if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)504err(1, "%s", test_id);505506/* Wait for the event */507kev.flags |= EV_CLEAR;508kev.fflags &= ~NOTE_USECONDS;509kev.data = 1;510kevent_cmp(&kev, kevent_get(kqfd));511elapsed = now() - start;512513/* Check that the timer expired after at least 1 ms. This check is514* to make sure that the timer re-started and that the event is515* not from the original add of the timer.516*/517printf("timer expired after %ld us\n", elapsed);518if (elapsed < MS_TO_US(1))519errx(1, "early timer expiration: %ld us", elapsed);520521success();522}523524static void525test_update_expired(void)526{527const char *test_id = "kevent(EVFILT_TIMER (UPDATE EXP), EV_ADD | EV_ONESHOT)";528struct kevent kev;529long elapsed;530uint64_t start;531532test_begin(test_id);533534test_no_kevents();535536/* Set the timer to 1ms */537EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,538NOTE_USECONDS, MS_TO_US(1), NULL);539if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)540err(1, "%s", test_id);541542/* Wait for 2 ms to give the timer plenty of time to expire. */543mssleep(2);544545/* Now re-add the timer */546start = now();547if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)548err(1, "%s", test_id);549550/* Wait for the event */551kev.flags |= EV_CLEAR;552kev.fflags &= ~NOTE_USECONDS;553kev.data = 1;554kevent_cmp(&kev, kevent_get(kqfd));555elapsed = now() - start;556557/* Check that the timer expired after at least 1 ms. This check558* is to make sure that the timer re-started and that the event is559* not from the original add (and expiration) of the timer.560*/561printf("timer expired after %ld us\n", elapsed);562if (elapsed < MS_TO_US(1))563errx(1, "early timer expiration: %ld us", elapsed);564565/* Make sure the re-added timer does not fire. In other words,566* test that the event received above was the only event from the567* add and re-add of the timer.568*/569mssleep(2);570test_no_kevents();571572success();573}574575static void576test_update_periodic(void)577{578const char *test_id = "kevent(EVFILT_TIMER (UPDATE), periodic)";579struct kevent kev;580long elapsed;581uint64_t start, stop;582583test_begin(test_id);584585test_no_kevents();586587EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, SEC_TO_MS(1), NULL);588if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)589err(1, "%s", test_id);590591/* Retrieve the event */592kev.flags = EV_ADD | EV_CLEAR;593kev.data = 1;594kevent_cmp(&kev, kevent_get(kqfd));595596/* Check if the event occurs again */597sleep(1);598kevent_cmp(&kev, kevent_get(kqfd));599600/* Re-add with new timeout. */601EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, SEC_TO_MS(2), NULL);602start = now();603if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)604err(1, "%s", test_id);605606/* Retrieve the event */607kev.flags = EV_ADD | EV_CLEAR;608kev.data = 1;609kevent_cmp(&kev, kevent_get(kqfd));610611stop = now();612elapsed = stop - start;613614/* Check that the timer expired after at least 2 ms.615*/616printf("timer expired after %ld us\n", elapsed);617if (elapsed < MS_TO_US(2))618errx(1, "early timer expiration: %ld us", elapsed);619620/* Delete the event */621kev.flags = EV_DELETE;622if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)623err(1, "%s", test_id);624625success();626}627628static void629test_update_timing(void)630{631#define MIN_SLEEP 500632#define MAX_SLEEP 1500633const char *test_id = "kevent(EVFILT_TIMER (UPDATE TIMING), EV_ADD | EV_ONESHOT)";634struct kevent kev;635int iteration;636int sleeptime;637long elapsed;638uint64_t start, stop;639640test_begin(test_id);641642test_no_kevents();643644/* Re-try the update tests with a variety of delays between the645* original timer activation and the update of the timer. The goal646* is to show that in all cases the only timer event that is647* received is from the update and not the original timer add.648*/649for (sleeptime = MIN_SLEEP, iteration = 1;650sleeptime < MAX_SLEEP;651++sleeptime, ++iteration) {652653/* First set the timer to 1 ms */654EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,655NOTE_USECONDS, MS_TO_US(1), NULL);656if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)657err(1, "%s", test_id);658659/* Delay; the delay ranges from less than to greater than the660* timer period.661*/662ussleep(sleeptime);663664/* Now re-add the timer with the same parameters */665start = now();666if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)667err(1, "%s", test_id);668669/* Wait for the event */670kev.flags |= EV_CLEAR;671kev.fflags &= ~NOTE_USECONDS;672kev.data = 1;673kevent_cmp(&kev, kevent_get(kqfd));674stop = now();675elapsed = stop - start;676677/* Check that the timer expired after at least 1 ms. This678* check is to make sure that the timer re-started and that679* the event is not from the original add of the timer.680*/681if (elapsed < MS_TO_US(1))682errx(1, "early timer expiration: %ld us", elapsed);683684/* Make sure the re-added timer does not fire. In other words,685* test that the event received above was the only event from686* the add and re-add of the timer.687*/688mssleep(2);689test_no_kevents_quietly();690}691692success();693}694695static void696test_dispatch(void)697{698const char *test_id = "kevent(EVFILT_TIMER, EV_ADD | EV_DISPATCH)";699struct kevent kev;700701test_no_kevents();702703EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_DISPATCH, 0, 200, NULL);704if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)705err(1, "%s", test_id);706707/* Get one event */708kev.flags = EV_ADD | EV_CLEAR | EV_DISPATCH;709kev.data = 1;710kevent_cmp(&kev, kevent_get(kqfd));711712/* Confirm that the knote is disabled due to EV_DISPATCH */713usleep(500000);714test_no_kevents();715716/* Enable the knote and make sure no events are pending */717EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ENABLE | EV_DISPATCH, 0, 200, NULL);718if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)719err(1, "%s", test_id);720test_no_kevents();721722/* Get the next event */723usleep(1100000); /* 1100 ms */724kev.flags = EV_ADD | EV_CLEAR | EV_DISPATCH;725kev.data = 5;726kevent_cmp(&kev, kevent_get(kqfd));727728/* Remove the knote and ensure the event no longer fires */729EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);730if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)731err(1, "%s", test_id);732usleep(500000); /* 500ms */733test_no_kevents();734735success();736}737738void739test_evfilt_timer(void)740{741kqfd = kqueue();742test_kevent_timer_add();743test_kevent_timer_del();744test_kevent_timer_get();745test_oneshot();746test_periodic();747test_periodic_modify();748#if WITH_NATIVE_KQUEUE_BUGS749test_periodic_to_oneshot();750#endif751test_abstime();752test_abstime_epoch();753test_abstime_preboot();754test_abstime_postboot();755test_update();756test_update_equal();757test_update_expired();758test_update_timing();759test_update_periodic();760test_disable_and_enable();761test_dispatch();762close(kqfd);763}764765766