blob: 3fb454076f0eabd0bf4ac6a94d61575340e49021 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <unistd.h>
#include <zxtest/zxtest.h>
TEST(EpollTest, EpollCreateBad) {
int epollfd = epoll_create(0);
ASSERT_EQ(epollfd, -1, "epoll_create() did not fail with 0 arg");
}
TEST(EpollTest, EpollCreateClose) {
int epollfd = epoll_create(1);
ASSERT_GT(epollfd, -1, "epoll_create() failed");
int closeres = close(epollfd);
ASSERT_EQ(closeres, 0, "epoll close() failed");
}
TEST(EpollTest, EpollWait) {
int epoll_fd = epoll_create(1);
ASSERT_NE(-1, epoll_fd);
epoll_event events[1];
// Regular epoll_wait.
ASSERT_EQ(0, epoll_wait(epoll_fd, events, 1, 1));
close(epoll_fd);
}
TEST(EpollTest, EpollEventNoData) {
int epoll_fd = epoll_create(1);
ASSERT_NE(-1, epoll_fd);
int fds[2];
ASSERT_NE(-1, pipe(fds));
const uint64_t expected = 0x123456789abcdef0;
// Get ready to poll on read end of pipe.
epoll_event ev;
ev.events = EPOLLIN;
ev.data.u64 = expected;
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fds[0], &ev));
// Poll.
epoll_event events[1];
ASSERT_EQ(0, epoll_wait(epoll_fd, events, 1, 1));
close(fds[0]);
close(fds[1]);
close(epoll_fd);
}
TEST(EpollTest, EpollEventData) {
int epoll_fd = epoll_create(1);
ASSERT_NE(-1, epoll_fd);
int fds[2];
ASSERT_NE(-1, pipe(fds));
const uint64_t expected = 0x123456789abcdef0;
// Get ready to poll on read end of pipe.
epoll_event ev;
ev.events = EPOLLIN;
ev.data.u64 = expected;
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fds[0], &ev));
// Ensure there's something in the pipe.
ASSERT_EQ(1, write(fds[1], "\n", 1));
// Poll.
epoll_event events[1];
events[0].events = 0;
events[0].data.u64 = 0;
ASSERT_EQ(1, epoll_wait(epoll_fd, events, 1, 1));
uint64_t observed = events[0].data.u64;
ASSERT_EQ(expected, observed);
char c;
ASSERT_EQ(1, read(fds[0], &c, sizeof(c)));
close(fds[0]);
close(fds[1]);
close(epoll_fd);
}
TEST(EpollTest, EpollEventDataMultiple) {
int epoll_fd = epoll_create(1);
ASSERT_NE(-1, epoll_fd);
int fds[2];
ASSERT_NE(-1, pipe(fds));
const uint64_t expected = 0x123456789abcdef0;
// Get ready to poll on read end of pipe.
epoll_event ev;
ev.events = EPOLLIN;
ev.data.u64 = expected;
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fds[0], &ev));
// Ensure there is something in the pipe.
ASSERT_EQ(1, write(fds[1], "\n", 1));
// Poll.
epoll_event events[1];
events[0].events = 0;
events[0].data.u64 = 0;
ASSERT_EQ(1, epoll_wait(epoll_fd, events, 1, 1));
uint64_t observed = events[0].data.u64;
ASSERT_EQ(expected, observed);
char c;
ASSERT_EQ(1, read(fds[0], &c, sizeof(c)));
// now there should be nothing to be read
ASSERT_EQ(0, epoll_wait(epoll_fd, events, 1, 1));
// now poll when there is something to be read
ASSERT_EQ(1, write(fds[1], "\n", 1));
events[0].events = 0;
events[0].data.u64 = 0;
ASSERT_EQ(1, epoll_wait(epoll_fd, events, 1, 1));
observed = events[0].data.u64;
ASSERT_EQ(expected, observed);
ASSERT_EQ(1, read(fds[0], &c, sizeof(c)));
close(fds[0]);
close(fds[1]);
close(epoll_fd);
}
TEST(EpollTest, EpollEventRemoveFd) {
int epoll_fd = epoll_create(1);
ASSERT_NE(-1, epoll_fd);
int fds[2];
ASSERT_NE(-1, pipe(fds));
const uint64_t expected = 0x123456789abcdef0;
// Get ready to poll on read end of pipe.
epoll_event ev;
ev.events = EPOLLIN;
ev.data.u64 = expected;
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fds[0], &ev));
// Ensure there is something in the pipe.
ASSERT_EQ(1, write(fds[1], "\n", 1));
// Now remove the fd
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fds[0], &ev));
// Poll.
epoll_event events[1];
events[0].events = 0;
events[0].data.u64 = 0;
ASSERT_EQ(0, epoll_wait(epoll_fd, events, 1, 1));
close(fds[0]);
close(fds[1]);
close(epoll_fd);
}
TEST(EpollTest, EpollEventWriteMod) {
int epoll_fd = epoll_create(1);
ASSERT_NE(-1, epoll_fd);
int fds[2];
ASSERT_NE(-1, pipe(fds));
const uint64_t unexpected = 0x1;
const uint64_t expected = 0x2;
// Get ready to poll on read end of pipe.
epoll_event ev;
ev.events = EPOLLIN;
ev.data.u64 = unexpected;
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fds[0], &ev));
// Ensure there is something in the pipe.
ASSERT_EQ(1, write(fds[1], "\n", 1));
ev.events = EPOLLIN;
ev.data.u64 = expected;
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fds[0], &ev));
// Poll.
epoll_event events[2];
events[0].events = 0;
events[0].data.u64 = 0;
events[1].events = 0;
events[1].data.u64 = 0;
ASSERT_EQ(1, epoll_wait(epoll_fd, events, 2, 1));
uint64_t observed = events[0].data.u64;
ASSERT_EQ(expected, observed);
close(fds[0]);
close(fds[1]);
close(epoll_fd);
}
TEST(EpollTest, EpollEventModWrite) {
int epoll_fd = epoll_create(1);
ASSERT_NE(-1, epoll_fd);
int fds[2];
ASSERT_NE(-1, pipe(fds));
const uint64_t unexpected = 0x1;
const uint64_t expected = 0x2;
// Get ready to poll on read end of pipe.
epoll_event ev;
ev.events = EPOLLIN;
ev.data.u64 = unexpected;
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fds[0], &ev));
ev.events = EPOLLIN;
ev.data.u64 = expected;
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fds[0], &ev));
ASSERT_EQ(1, write(fds[1], "\n", 1));
// Poll.
epoll_event events[2];
events[0].events = 0;
events[0].data.u64 = 0;
events[1].events = 0;
events[1].data.u64 = 0;
ASSERT_EQ(1, epoll_wait(epoll_fd, events, 2, 1));
uint64_t observed = events[0].data.u64;
ASSERT_EQ(expected, observed);
close(fds[0]);
close(fds[1]);
close(epoll_fd);
}
TEST(EpollTest, EpollEdgeTriggered) {
int epoll_fd = epoll_create(1);
ASSERT_NE(-1, epoll_fd);
int fds[2];
ASSERT_NE(-1, pipe(fds));
const uint64_t expected = 0x123456789abcdef0;
epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.u64 = expected;
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fds[0], &ev));
ASSERT_EQ(1, write(fds[1], "aa", 1));
epoll_event events[2];
ASSERT_EQ(1, epoll_wait(epoll_fd, events, 2, 1));
uint64_t observed = events[0].data.u64;
ASSERT_EQ(expected, observed);
char c;
ASSERT_EQ(1, read(fds[0], &c, sizeof(c)));
// no new writes yet
ASSERT_EQ(0, epoll_wait(epoll_fd, events, 2, 1));
ASSERT_EQ(1, write(fds[1], "a", 1));
ASSERT_EQ(1, epoll_wait(epoll_fd, events, 2, 1));
// event has been consumed
ASSERT_EQ(0, epoll_wait(epoll_fd, events, 2, 1));
close(fds[0]);
close(fds[1]);
close(epoll_fd);
}
// This identical to |EpollEdgeTriggered| but we make
// The fd ready prior to calling epoll_ctl.
TEST(EpollTest, EpollEdgeFdReady) {
int epoll_fd = epoll_create(1);
ASSERT_NE(-1, epoll_fd);
int fds[2];
ASSERT_NE(-1, pipe(fds));
const uint64_t expected = 0x123456789abcdef0;
epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.u64 = expected;
// This makes fds[0] ready before calling epoll_ctl.
ASSERT_EQ(1, write(fds[1], "aa", 1));
// This should queue a packet immediately.
ASSERT_NE(-1, epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fds[0], &ev));
epoll_event events[2];
ASSERT_EQ(1, epoll_wait(epoll_fd, events, 2, 1));
uint64_t observed = events[0].data.u64;
ASSERT_EQ(expected, observed);
char c;
ASSERT_EQ(1, read(fds[0], &c, sizeof(c)));
// no new writes yet
ASSERT_EQ(0, epoll_wait(epoll_fd, events, 2, 1));
ASSERT_EQ(1, write(fds[1], "a", 1));
ASSERT_EQ(1, epoll_wait(epoll_fd, events, 2, 1));
// event has been consumed
ASSERT_EQ(0, epoll_wait(epoll_fd, events, 2, 1));
close(fds[0]);
close(fds[1]);
close(epoll_fd);
}
TEST(EpollTest, EventFdEdgeTriggeredEveryWriteGeneratesEvent) {
// Verify that we receive EPOLLIN for every new write
// on an eventfd even if it is not read.
int pollfd = epoll_create(1);
ASSERT_NE(pollfd, -1);
int evfd = eventfd(0, EFD_NONBLOCK);
ASSERT_NE(evfd, -1);
epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = nullptr;
int ret = epoll_ctl(pollfd, EPOLL_CTL_ADD, evfd, &ev);
ASSERT_NE(ret, -1);
// On Linux, every iteration through the test loop produces a new EPOLLIN event.
#if defined(__linux__)
constexpr int iteration_count = 10;
#else
// https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=64286
// Fuchsia currently does not implement this behavior correctly and only
// produces an EPOLLIN event on the first write.
constexpr int iteration_count = 1;
#endif
for (int i = 0; i < iteration_count; ++i) {
int n;
do {
n = eventfd_write(evfd, 1);
} while (n < 0 && errno == EINTR);
ASSERT_EQ(n, 0);
struct epoll_event events[1];
do {
n = epoll_wait(pollfd, events, 1, 100);
} while (n < 0 && errno == EINTR);
ASSERT_EQ(n, 1);
ASSERT_TRUE(events[0].events & EPOLLIN);
}
}
TEST(EpollTest, CloseFileDescriptorInsideEpollSet) {
// This tests a surprising behavior / design bug in epoll(). In Linux, entries
// in the epoll interest set are registered on the file descriptor and not the
// file description. This means close() which operates on a file descriptor,
// does not actually remove the entry from the epoll set.
// Illumos decided not to implement this quirk and instead registers file
// descriptors into the epoll set instead of file descriptions:
// https://illumos.org/man/5/epoll
int fds[2];
ASSERT_NE(-1, pipe(fds));
int epoll_fd = epoll_create(1);
ASSERT_NE(-1, epoll_fd);
// Register the file description referred to by the file descriptor fds[0].
epoll_event ev = {};
ev.events = EPOLLIN;
ASSERT_EQ(0, epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fds[0], &ev));
// Duplicate fds[0] to produce a second file descriptor referring to the same file description.
int duplicate = dup(fds[0]);
ASSERT_NE(-1, duplicate);
// Close the original file descriptor.
ASSERT_EQ(0, close(fds[0]));
// At this point the epoll entry is still active and cannot be removed from epoll_fd.
EXPECT_EQ(0, epoll_wait(epoll_fd, &ev, 1, 0));
char c = 'a';
EXPECT_EQ(1, write(fds[1], &c, 1));
#if defined(__linux__)
// TODO(https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=64296):
// The current Fuchsia implementation does not receive this event.
EXPECT_EQ(1, epoll_wait(epoll_fd, &ev, 1, 0));
EXPECT_EQ(EPOLLIN, ev.events);
#endif // defined(__linux__)
close(epoll_fd);
close(fds[1]);
close(duplicate);
}