summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/timerfd.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/timerfd.cc')
-rw-r--r--test/syscalls/linux/timerfd.cc273
1 files changed, 273 insertions, 0 deletions
diff --git a/test/syscalls/linux/timerfd.cc b/test/syscalls/linux/timerfd.cc
new file mode 100644
index 000000000..c4f8fdd7a
--- /dev/null
+++ b/test/syscalls/linux/timerfd.cc
@@ -0,0 +1,273 @@
+// Copyright 2018 The gVisor Authors.
+//
+// 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 <poll.h>
+#include <sys/timerfd.h>
+#include <time.h>
+
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "test/util/file_descriptor.h"
+#include "test/util/posix_error.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+// Wrapper around timerfd_create(2) that returns a FileDescriptor.
+PosixErrorOr<FileDescriptor> TimerfdCreate(int clockid, int flags) {
+ int fd = timerfd_create(clockid, flags);
+ MaybeSave();
+ if (fd < 0) {
+ return PosixError(errno, "timerfd_create failed");
+ }
+ return FileDescriptor(fd);
+}
+
+// In tests that race a timerfd with a sleep, some slack is required because:
+//
+// - Timerfd expirations are asynchronous with respect to nanosleeps.
+//
+// - Because clock_gettime(CLOCK_MONOTONIC) is implemented through the VDSO,
+// it technically uses a closely-related, but distinct, time domain from the
+// CLOCK_MONOTONIC used to trigger timerfd expirations. The same applies to
+// CLOCK_BOOTTIME which is an alias for CLOCK_MONOTONIC.
+absl::Duration TimerSlack() { return absl::Milliseconds(500); }
+
+class TimerfdTest : public ::testing::TestWithParam<int> {};
+
+TEST_P(TimerfdTest, IsInitiallyStopped) {
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
+ struct itimerspec its = {};
+ ASSERT_THAT(timerfd_gettime(tfd.get(), &its), SyscallSucceeds());
+ EXPECT_EQ(0, its.it_value.tv_sec);
+ EXPECT_EQ(0, its.it_value.tv_nsec);
+}
+
+TEST_P(TimerfdTest, SingleShot) {
+ constexpr absl::Duration kDelay = absl::Seconds(1);
+
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
+ struct itimerspec its = {};
+ its.it_value = absl::ToTimespec(kDelay);
+ ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
+ SyscallSucceeds());
+
+ // The timer should fire exactly once since the interval is zero.
+ absl::SleepFor(kDelay + TimerSlack());
+ uint64_t val = 0;
+ ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
+ SyscallSucceedsWithValue(sizeof(uint64_t)));
+ EXPECT_EQ(1, val);
+}
+
+TEST_P(TimerfdTest, Periodic) {
+ constexpr absl::Duration kDelay = absl::Seconds(1);
+ constexpr int kPeriods = 3;
+
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
+ struct itimerspec its = {};
+ its.it_value = absl::ToTimespec(kDelay);
+ its.it_interval = absl::ToTimespec(kDelay);
+ ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
+ SyscallSucceeds());
+
+ // Expect to see at least kPeriods expirations. More may occur due to the
+ // timer slack, or due to delays from scheduling or save/restore.
+ absl::SleepFor(kPeriods * kDelay + TimerSlack());
+ uint64_t val = 0;
+ ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
+ SyscallSucceedsWithValue(sizeof(uint64_t)));
+ EXPECT_GE(val, kPeriods);
+}
+
+TEST_P(TimerfdTest, BlockingRead) {
+ constexpr absl::Duration kDelay = absl::Seconds(3);
+
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
+ struct itimerspec its = {};
+ its.it_value.tv_sec = absl::ToInt64Seconds(kDelay);
+ auto const start_time = absl::Now();
+ ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
+ SyscallSucceeds());
+
+ // read should block until the timer fires.
+ uint64_t val = 0;
+ ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
+ SyscallSucceedsWithValue(sizeof(uint64_t)));
+ auto const end_time = absl::Now();
+ EXPECT_EQ(1, val);
+ EXPECT_GE((end_time - start_time) + TimerSlack(), kDelay);
+}
+
+TEST_P(TimerfdTest, NonblockingRead_NoRandomSave) {
+ constexpr absl::Duration kDelay = absl::Seconds(5);
+
+ auto const tfd =
+ ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK));
+
+ // Since the timer is initially disabled and has never fired, read should
+ // return EAGAIN.
+ uint64_t val = 0;
+ ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
+ SyscallFailsWithErrno(EAGAIN));
+
+ DisableSave ds; // Timing-sensitive.
+
+ // Arm the timer.
+ struct itimerspec its = {};
+ its.it_value.tv_sec = absl::ToInt64Seconds(kDelay);
+ ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
+ SyscallSucceeds());
+
+ // Since the timer has not yet fired, read should return EAGAIN.
+ ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
+ SyscallFailsWithErrno(EAGAIN));
+
+ ds.reset(); // No longer timing-sensitive.
+
+ // After the timer fires, read should indicate 1 expiration.
+ absl::SleepFor(kDelay + TimerSlack());
+ ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
+ SyscallSucceedsWithValue(sizeof(uint64_t)));
+ EXPECT_EQ(1, val);
+
+ // The successful read should have reset the number of expirations.
+ ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
+ SyscallFailsWithErrno(EAGAIN));
+}
+
+TEST_P(TimerfdTest, BlockingPoll_SetTimeResetsExpirations) {
+ constexpr absl::Duration kDelay = absl::Seconds(3);
+
+ auto const tfd =
+ ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK));
+ struct itimerspec its = {};
+ its.it_value.tv_sec = absl::ToInt64Seconds(kDelay);
+ auto const start_time = absl::Now();
+ ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
+ SyscallSucceeds());
+
+ // poll should block until the timer fires.
+ struct pollfd pfd = {};
+ pfd.fd = tfd.get();
+ pfd.events = POLLIN;
+ ASSERT_THAT(poll(&pfd, /* nfds = */ 1,
+ /* timeout = */ 2 * absl::ToInt64Seconds(kDelay) * 1000),
+ SyscallSucceedsWithValue(1));
+ auto const end_time = absl::Now();
+ EXPECT_EQ(POLLIN, pfd.revents);
+ EXPECT_GE((end_time - start_time) + TimerSlack(), kDelay);
+
+ // Call timerfd_settime again with a value of 0. This should reset the number
+ // of expirations to 0, causing read to return EAGAIN since the timerfd is
+ // non-blocking.
+ its.it_value.tv_sec = 0;
+ ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
+ SyscallSucceeds());
+ uint64_t val = 0;
+ ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
+ SyscallFailsWithErrno(EAGAIN));
+}
+
+TEST_P(TimerfdTest, SetAbsoluteTime) {
+ constexpr absl::Duration kDelay = absl::Seconds(3);
+
+ // Use a non-blocking timerfd so that if TFD_TIMER_ABSTIME is incorrectly
+ // non-functional, we get EAGAIN rather than a test timeout.
+ auto const tfd =
+ ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK));
+ struct itimerspec its = {};
+ ASSERT_THAT(clock_gettime(GetParam(), &its.it_value), SyscallSucceeds());
+ its.it_value.tv_sec += absl::ToInt64Seconds(kDelay);
+ ASSERT_THAT(timerfd_settime(tfd.get(), TFD_TIMER_ABSTIME, &its, nullptr),
+ SyscallSucceeds());
+
+ absl::SleepFor(kDelay + TimerSlack());
+ uint64_t val = 0;
+ ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
+ SyscallSucceedsWithValue(sizeof(uint64_t)));
+ EXPECT_EQ(1, val);
+}
+
+TEST_P(TimerfdTest, IllegalSeek) {
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
+ if (!IsRunningWithVFS1()) {
+ EXPECT_THAT(lseek(tfd.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE));
+ }
+}
+
+TEST_P(TimerfdTest, IllegalPread) {
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
+ int val;
+ EXPECT_THAT(pread(tfd.get(), &val, sizeof(val), 0),
+ SyscallFailsWithErrno(ESPIPE));
+}
+
+TEST_P(TimerfdTest, IllegalPwrite) {
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
+ EXPECT_THAT(pwrite(tfd.get(), "x", 1, 0), SyscallFailsWithErrno(ESPIPE));
+ if (!IsRunningWithVFS1()) {
+ }
+}
+
+TEST_P(TimerfdTest, IllegalWrite) {
+ auto const tfd =
+ ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK));
+ uint64_t val = 0;
+ EXPECT_THAT(write(tfd.get(), &val, sizeof(val)),
+ SyscallFailsWithErrno(EINVAL));
+}
+
+std::string PrintClockId(::testing::TestParamInfo<int> info) {
+ switch (info.param) {
+ case CLOCK_MONOTONIC:
+ return "CLOCK_MONOTONIC";
+ case CLOCK_BOOTTIME:
+ return "CLOCK_BOOTTIME";
+ default:
+ return absl::StrCat(info.param);
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(AllTimerTypes, TimerfdTest,
+ ::testing::Values(CLOCK_MONOTONIC, CLOCK_BOOTTIME),
+ PrintClockId);
+
+TEST(TimerfdClockRealtimeTest, ClockRealtime) {
+ // Since CLOCK_REALTIME can, by definition, change, we can't make any
+ // non-flaky assertions about the amount of time it takes for a
+ // CLOCK_REALTIME-based timer to expire. Just check that it expires at all,
+ // and hope it happens before the test times out.
+ constexpr int kDelaySecs = 1;
+
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_REALTIME, 0));
+ struct itimerspec its = {};
+ its.it_value.tv_sec = kDelaySecs;
+ ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
+ SyscallSucceeds());
+
+ uint64_t val = 0;
+ ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
+ SyscallSucceedsWithValue(sizeof(uint64_t)));
+ EXPECT_EQ(1, val);
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor