// evtest: eventfd, timerfd, signalfd and epoll, each checked for the one // property an event loop depends on, and then all four in one epoll set -- // which is how libwayland's event loop uses them. // // A musl program, linked dynamically, so it goes through libc's wrappers // exactly as a ported program would. #include #include #include #include #include #include #include #include #include #include #include static int bad; #define CHECK(cond, ...) do { \ if (!(cond)) { printf("evtest: FAIL " __VA_ARGS__); printf("\n"); bad++; } \ } while (0) static long ms_since(struct timespec *t0) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return (t.tv_sec - t0->tv_sec) * 1000 + (t.tv_nsec - t0->tv_nsec) / 1000000; } int main(void) { uint64_t v; struct timespec t0; // eventfd: writes add, a read takes the sum and empties it, and an empty // nonblocking one says EAGAIN rather than blocking. int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); CHECK(efd >= 0, "eventfd: %d", errno); v = 3; write(efd, &v, 8); v = 4; write(efd, &v, 8); v = 0; CHECK(read(efd, &v, 8) == 8 && v == 7, "eventfd sum %llu", (unsigned long long)v); CHECK(read(efd, &v, 8) < 0 && errno == EAGAIN, "eventfd empty: errno %d", errno); int sem = eventfd(2, EFD_SEMAPHORE); CHECK(read(sem, &v, 8) == 8 && v == 1, "semaphore eventfd gave %llu", (unsigned long long)v); // timerfd: a one-shot 50 ms timer is not ready at once, is after it, and a // blocking read waits for it rather than returning. int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); CHECK(tfd >= 0, "timerfd_create: %d", errno); struct itimerspec its = { { 0, 0 }, { 0, 50 * 1000000 } }; clock_gettime(CLOCK_MONOTONIC, &t0); CHECK(timerfd_settime(tfd, 0, &its, 0) == 0, "timerfd_settime: %d", errno); v = 0; CHECK(read(tfd, &v, 8) == 8 && v == 1, "timerfd expirations %llu", (unsigned long long)v); long waited = ms_since(&t0); CHECK(waited >= 40 && waited < 1000, "timerfd fired after %ld ms", waited); // signalfd: a blocked SIGUSR1 sent to ourselves is read back as data. sigset_t m; sigemptyset(&m); sigaddset(&m, SIGUSR1); sigprocmask(SIG_BLOCK, &m, 0); int sfd = signalfd(-1, &m, SFD_NONBLOCK); CHECK(sfd >= 0, "signalfd: %d", errno); kill(getpid(), SIGUSR1); struct signalfd_siginfo si; memset(&si, 0, sizeof(si)); CHECK(read(sfd, &si, sizeof(si)) == sizeof(si) && si.ssi_signo == SIGUSR1, "signalfd read signo %u", si.ssi_signo); // epoll: all of it in one set. Nothing ready -> times out; then the // timer, the eventfd and the signal each arrive as their own event, // carrying the data they were registered with. int ep = epoll_create1(EPOLL_CLOEXEC); CHECK(ep >= 0, "epoll_create1: %d", errno); struct epoll_event ev; ev.events = EPOLLIN; ev.data.u64 = 1; epoll_ctl(ep, EPOLL_CTL_ADD, efd, &ev); ev.events = EPOLLIN; ev.data.u64 = 2; epoll_ctl(ep, EPOLL_CTL_ADD, tfd, &ev); ev.events = EPOLLIN; ev.data.u64 = 3; epoll_ctl(ep, EPOLL_CTL_ADD, sfd, &ev); CHECK(epoll_ctl(ep, EPOLL_CTL_ADD, efd, &ev) < 0 && errno == EEXIST, "double add"); struct epoll_event out[8]; clock_gettime(CLOCK_MONOTONIC, &t0); CHECK(epoll_wait(ep, out, 8, 30) == 0, "an empty set was ready"); CHECK(ms_since(&t0) >= 20, "epoll_wait timed out after %ld ms", ms_since(&t0)); its.it_value.tv_nsec = 20 * 1000000; timerfd_settime(tfd, 0, &its, 0); int n = epoll_wait(ep, out, 8, 1000); CHECK(n == 1 && out[0].data.u64 == 2, "timer through epoll: n=%d data=%llu", n, n > 0 ? (unsigned long long)out[0].data.u64 : 0ULL); read(tfd, &v, 8); v = 1; write(efd, &v, 8); kill(getpid(), SIGUSR1); n = epoll_wait(ep, out, 8, 1000); int seen = 0; for (int i = 0; i < n; i++) seen |= 1 << out[i].data.u64; CHECK(n == 2 && seen == ((1 << 1) | (1 << 3)), "eventfd+signal: n=%d mask=%x", n, seen); // Level-triggered: still ready until drained. Then edge-triggered: once. n = epoll_wait(ep, out, 8, 0); CHECK(n == 2, "level-triggered repeat gave %d", n); read(efd, &v, 8); read(sfd, &si, sizeof(si)); ev.events = EPOLLIN | EPOLLET; ev.data.u64 = 1; epoll_ctl(ep, EPOLL_CTL_MOD, efd, &ev); v = 1; write(efd, &v, 8); CHECK(epoll_wait(ep, out, 8, 0) == 1, "edge-triggered first report"); CHECK(epoll_wait(ep, out, 8, 0) == 0, "edge-triggered reported twice"); // A closed descriptor leaves the set. close(efd); CHECK(epoll_wait(ep, out, 8, 0) == 0, "closed fd still in the set"); CHECK(epoll_ctl(ep, EPOLL_CTL_DEL, tfd, 0) == 0, "EPOLL_CTL_DEL"); printf("evtest: %s\n", bad ? "FAILED" : "ok"); return bad != 0; }