The set libwayland's event loop is made of, in kernel/sys/sys_event.c: four kinds of descriptor with no inode, FD_EVENT, that read() drains and poll() reports on. They were table entries returning ENOSYS that read as done. epoll registers (descriptor, file) pairs, so a closed descriptor leaves the set; level- and edge-triggered, EPOLLONESHOT. Waiting is poll()'s, a tick at a time. The old eventfd and signalfd numbers (284, 282) were aliased to the new calls and read flags from a garbage register; they are their own entries now, with epoll_create and epoll_pwait. cmd/evtest checks each, then all four in one set. The plans are brought up to date: everything Wayland was waiting on is built except setsockopt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
128 lines
4.9 KiB
C
128 lines
4.9 KiB
C
// 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 <errno.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/eventfd.h>
|
|
#include <sys/signalfd.h>
|
|
#include <sys/timerfd.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
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;
|
|
}
|