diff options
Diffstat (limited to 'test/syscalls/linux/epoll.cc')
-rw-r--r-- | test/syscalls/linux/epoll.cc | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc new file mode 100644 index 000000000..9ae87c00b --- /dev/null +++ b/test/syscalls/linux/epoll.cc @@ -0,0 +1,468 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <errno.h> +#include <limits.h> +#include <pthread.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/eventfd.h> +#include <time.h> +#include <unistd.h> + +#include "gtest/gtest.h" +#include "test/util/file_descriptor.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +constexpr int kFDsPerEpoll = 3; +constexpr uint64_t kMagicConstant = 0x0102030405060708; + +// Returns a new epoll file descriptor. +PosixErrorOr<FileDescriptor> NewEpollFD() { + // "Since Linux 2.6.8, the size argument is ignored, but must be greater than + // zero." - epoll_create(2) + int fd = epoll_create(/* size = */ 1); + MaybeSave(); + if (fd < 0) { + return PosixError(errno, "epoll_create"); + } + return FileDescriptor(fd); +} + +// Returns a new eventfd. +PosixErrorOr<FileDescriptor> NewEventFD() { + int fd = eventfd(/* initval = */ 0, /* flags = */ 0); + MaybeSave(); + if (fd < 0) { + return PosixError(errno, "eventfd"); + } + return FileDescriptor(fd); +} + +// Registers `target_fd` with the epoll instance represented by `epoll_fd` for +// the epoll events `events`. Events on `target_fd` will be indicated by setting +// data.u64 to `data` in the returned epoll_event. +PosixError RegisterEpollFD(int epoll_fd, int target_fd, int events, + uint64_t data) { + struct epoll_event event; + event.events = events; + event.data.u64 = data; + int rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, target_fd, &event); + MaybeSave(); + if (rc < 0) { + return PosixError(errno, "epoll_ctl"); + } + return NoError(); +} + +uint64_t ms_elapsed(const struct timespec* begin, const struct timespec* end) { + return (end->tv_sec - begin->tv_sec) * 1000 + + (end->tv_nsec - begin->tv_nsec) / 1000000; +} + +TEST(EpollTest, AllWritable) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + std::vector<FileDescriptor> eventfds; + for (int i = 0; i < kFDsPerEpoll; i++) { + eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); + ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), + EPOLLIN | EPOLLOUT, kMagicConstant + i)); + } + + struct epoll_event result[kFDsPerEpoll]; + ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(kFDsPerEpoll)); + // TODO: Why do some tests check epoll_event::data, and others + // don't? Does Linux actually guarantee that, in any of these test cases, + // epoll_wait will necessarily write out the epoll_events in the order that + // they were registered? + for (int i = 0; i < kFDsPerEpoll; i++) { + ASSERT_EQ(result[i].events, EPOLLOUT); + } +} + +TEST(EpollTest, LastReadable) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + std::vector<FileDescriptor> eventfds; + for (int i = 0; i < kFDsPerEpoll; i++) { + eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); + ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), + EPOLLIN | EPOLLOUT, kMagicConstant + i)); + } + + uint64_t tmp = 1; + ASSERT_THAT(WriteFd(eventfds[kFDsPerEpoll - 1].get(), &tmp, sizeof(tmp)), + SyscallSucceedsWithValue(sizeof(tmp))); + + struct epoll_event result[kFDsPerEpoll]; + ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(kFDsPerEpoll)); + + int i; + for (i = 0; i < kFDsPerEpoll - 1; i++) { + EXPECT_EQ(result[i].events, EPOLLOUT); + } + EXPECT_EQ(result[i].events, EPOLLOUT | EPOLLIN); + EXPECT_EQ(result[i].data.u64, kMagicConstant + i); +} + +TEST(EpollTest, LastNonWritable) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + std::vector<FileDescriptor> eventfds; + for (int i = 0; i < kFDsPerEpoll; i++) { + eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); + ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), + EPOLLIN | EPOLLOUT, kMagicConstant + i)); + } + + // Write the maximum value to the event fd so that writing to it again would + // block. + uint64_t tmp = ULLONG_MAX - 1; + ASSERT_THAT(WriteFd(eventfds[kFDsPerEpoll - 1].get(), &tmp, sizeof(tmp)), + SyscallSucceedsWithValue(sizeof(tmp))); + + struct epoll_event result[kFDsPerEpoll]; + ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(kFDsPerEpoll)); + + int i; + for (i = 0; i < kFDsPerEpoll - 1; i++) { + EXPECT_EQ(result[i].events, EPOLLOUT); + } + EXPECT_EQ(result[i].events, EPOLLIN); + EXPECT_THAT(ReadFd(eventfds[kFDsPerEpoll - 1].get(), &tmp, sizeof(tmp)), + sizeof(tmp)); + EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(kFDsPerEpoll)); + + for (i = 0; i < kFDsPerEpoll; i++) { + EXPECT_EQ(result[i].events, EPOLLOUT); + } +} + +TEST(EpollTest, Timeout_NoRandomSave) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + std::vector<FileDescriptor> eventfds; + for (int i = 0; i < kFDsPerEpoll; i++) { + eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); + ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, + kMagicConstant + i)); + } + + constexpr int kTimeoutMs = 200; + struct timespec begin; + struct timespec end; + struct epoll_event result[kFDsPerEpoll]; + + { + const DisableSave ds; // Timing-related. + EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &begin), SyscallSucceeds()); + + ASSERT_THAT( + RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, kTimeoutMs), + SyscallSucceedsWithValue(0)); + EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &end), SyscallSucceeds()); + } + + // Check the lower bound on the timeout. Checking for an upper bound is + // fragile because Linux can overrun the timeout due to scheduling delays. + EXPECT_GT(ms_elapsed(&begin, &end), kTimeoutMs - 1); +} + +void* writer(void* arg) { + int fd = *reinterpret_cast<int*>(arg); + uint64_t tmp = 1; + + usleep(200000); + if (WriteFd(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) { + fprintf(stderr, "writer failed: errno %s\n", strerror(errno)); + } + + return nullptr; +} + +TEST(EpollTest, WaitThenUnblock) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + std::vector<FileDescriptor> eventfds; + for (int i = 0; i < kFDsPerEpoll; i++) { + eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); + ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, + kMagicConstant + i)); + } + + // Fire off a thread that will make at least one of the event fds readable. + pthread_t thread; + int make_readable = eventfds[0].get(); + ASSERT_THAT(pthread_create(&thread, nullptr, writer, &make_readable), + SyscallSucceedsWithValue(0)); + + struct epoll_event result[kFDsPerEpoll]; + EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(1)); + EXPECT_THAT(pthread_detach(thread), SyscallSucceeds()); +} + +void sighandler(int s) {} + +void* signaler(void* arg) { + pthread_t* t = reinterpret_cast<pthread_t*>(arg); + // Repeatedly send the real-time signal until we are detached, because it's + // difficult to know exactly when epoll_wait on another thread (which this + // is intending to interrupt) has started blocking. + while (1) { + usleep(200000); + pthread_kill(*t, SIGRTMIN); + } + return nullptr; +} + +TEST(EpollTest, UnblockWithSignal) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + std::vector<FileDescriptor> eventfds; + for (int i = 0; i < kFDsPerEpoll; i++) { + eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); + ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, + kMagicConstant + i)); + } + + signal(SIGRTMIN, sighandler); + // Unblock the real time signals that InitGoogle blocks :( + sigset_t unblock; + sigemptyset(&unblock); + sigaddset(&unblock, SIGRTMIN); + ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &unblock, nullptr), SyscallSucceeds()); + + pthread_t thread; + pthread_t cur = pthread_self(); + ASSERT_THAT(pthread_create(&thread, nullptr, signaler, &cur), + SyscallSucceedsWithValue(0)); + + struct epoll_event result[kFDsPerEpoll]; + EXPECT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallFailsWithErrno(EINTR)); + EXPECT_THAT(pthread_cancel(thread), SyscallSucceeds()); + EXPECT_THAT(pthread_detach(thread), SyscallSucceeds()); +} + +TEST(EpollTest, TimeoutNoFds) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + struct epoll_event result[kFDsPerEpoll]; + EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100), + SyscallSucceedsWithValue(0)); +} + +struct addr_ctx { + int epollfd; + int eventfd; +}; + +void* fd_adder(void* arg) { + struct addr_ctx* actx = reinterpret_cast<struct addr_ctx*>(arg); + struct epoll_event event; + event.events = EPOLLIN | EPOLLOUT; + event.data.u64 = 0xdeadbeeffacefeed; + + usleep(200000); + if (epoll_ctl(actx->epollfd, EPOLL_CTL_ADD, actx->eventfd, &event) == -1) { + fprintf(stderr, "epoll_ctl failed: %s\n", strerror(errno)); + } + + return nullptr; +} + +TEST(EpollTest, UnblockWithNewFD) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); + + pthread_t thread; + struct addr_ctx actx = {epollfd.get(), eventfd.get()}; + ASSERT_THAT(pthread_create(&thread, nullptr, fd_adder, &actx), + SyscallSucceedsWithValue(0)); + + struct epoll_event result[kFDsPerEpoll]; + // Wait while no FDs are ready, but after 200ms fd_adder will add a ready FD + // to epoll which will wake us up. + EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(1)); + EXPECT_THAT(pthread_detach(thread), SyscallSucceeds()); + EXPECT_EQ(result[0].data.u64, 0xdeadbeeffacefeed); +} + +TEST(EpollTest, Oneshot) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + std::vector<FileDescriptor> eventfds; + for (int i = 0; i < kFDsPerEpoll; i++) { + eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); + ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, + kMagicConstant + i)); + } + + struct epoll_event event; + event.events = EPOLLOUT | EPOLLONESHOT; + event.data.u64 = kMagicConstant; + ASSERT_THAT( + epoll_ctl(epollfd.get(), EPOLL_CTL_MOD, eventfds[0].get(), &event), + SyscallSucceeds()); + + struct epoll_event result[kFDsPerEpoll]; + // One-shot entry means that the first epoll_wait should succeed. + ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(1)); + EXPECT_EQ(result[0].data.u64, kMagicConstant); + + // One-shot entry means that the second epoll_wait should timeout. + EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100), + SyscallSucceedsWithValue(0)); +} + +TEST(EpollTest, EdgeTriggered_NoRandomSave) { + // Test edge-triggered entry: make it edge-triggered, first wait should + // return it, second one should time out, make it writable again, third wait + // should return it, fourth wait should timeout. + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); + ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfd.get(), + EPOLLOUT | EPOLLET, kMagicConstant)); + + struct epoll_event result[kFDsPerEpoll]; + + { + const DisableSave ds; // May trigger spurious event. + + // Edge-triggered entry means that the first epoll_wait should return the + // event. + ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(1)); + EXPECT_EQ(result[0].data.u64, kMagicConstant); + + // Edge-triggered entry means that the second epoll_wait should time out. + ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, 100), + SyscallSucceedsWithValue(0)); + } + + uint64_t tmp = ULLONG_MAX - 1; + + // Make an fd non-writable. + ASSERT_THAT(WriteFd(eventfd.get(), &tmp, sizeof(tmp)), + SyscallSucceedsWithValue(sizeof(tmp))); + + // Make the same fd non-writable to trigger a change, which will trigger an + // edge-triggered event. + ASSERT_THAT(ReadFd(eventfd.get(), &tmp, sizeof(tmp)), + SyscallSucceedsWithValue(sizeof(tmp))); + + { + const DisableSave ds; // May trigger spurious event. + + // An edge-triggered event should now be returned. + ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(1)); + EXPECT_EQ(result[0].data.u64, kMagicConstant); + + // The edge-triggered event had been consumed above, we don't expect to + // get it again. + ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, 100), + SyscallSucceedsWithValue(0)); + } +} + +TEST(EpollTest, OneshotAndEdgeTriggered) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); + ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfd.get(), + EPOLLOUT | EPOLLET | EPOLLONESHOT, + kMagicConstant)); + + struct epoll_event result[kFDsPerEpoll]; + // First time one shot edge-triggered entry means that epoll_wait should + // return the event. + ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(1)); + EXPECT_EQ(result[0].data.u64, kMagicConstant); + + // Edge-triggered entry means that the second epoll_wait should time out. + ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100), + SyscallSucceedsWithValue(0)); + + uint64_t tmp = ULLONG_MAX - 1; + // Make an fd non-writable. + ASSERT_THAT(WriteFd(eventfd.get(), &tmp, sizeof(tmp)), + SyscallSucceedsWithValue(sizeof(tmp))); + // Make the same fd non-writable to trigger a change, which will not trigger + // an edge-triggered event because we've also included EPOLLONESHOT. + ASSERT_THAT(ReadFd(eventfd.get(), &tmp, sizeof(tmp)), + SyscallSucceedsWithValue(sizeof(tmp))); + ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100), + SyscallSucceedsWithValue(0)); +} + +TEST(EpollTest, CycleOfOneDisallowed) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + + struct epoll_event event; + event.events = EPOLLOUT; + event.data.u64 = kMagicConstant; + + ASSERT_THAT(epoll_ctl(epollfd.get(), EPOLL_CTL_ADD, epollfd.get(), &event), + SyscallFailsWithErrno(EINVAL)); +} + +TEST(EpollTest, CycleOfThreeDisallowed) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + auto epollfd1 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + auto epollfd2 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + + ASSERT_NO_ERRNO( + RegisterEpollFD(epollfd.get(), epollfd1.get(), EPOLLIN, kMagicConstant)); + ASSERT_NO_ERRNO( + RegisterEpollFD(epollfd1.get(), epollfd2.get(), EPOLLIN, kMagicConstant)); + + struct epoll_event event; + event.events = EPOLLIN; + event.data.u64 = kMagicConstant; + EXPECT_THAT(epoll_ctl(epollfd2.get(), EPOLL_CTL_ADD, epollfd.get(), &event), + SyscallFailsWithErrno(ELOOP)); +} + +TEST(EpollTest, CloseFile) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); + ASSERT_NO_ERRNO( + RegisterEpollFD(epollfd.get(), eventfd.get(), EPOLLOUT, kMagicConstant)); + + struct epoll_event result[kFDsPerEpoll]; + ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), + SyscallSucceedsWithValue(1)); + EXPECT_EQ(result[0].data.u64, kMagicConstant); + + // Close the event fd early. + eventfd.reset(); + + EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100), + SyscallSucceedsWithValue(0)); +} + +} // namespace + +} // namespace testing +} // namespace gvisor |