diff options
author | Brian Geffon <bgeffon@google.com> | 2018-12-10 14:41:40 -0800 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-12-10 14:42:34 -0800 |
commit | d3bc79bc8438206ac6a14fde4eaa288fc07eee82 (patch) | |
tree | e820398591bfd1503456e877fa0c2bdd0f994959 /test/syscalls/linux/timers.cc | |
parent | 833edbd10b49db1f934dcb2495dcb41c1310eea4 (diff) |
Open source system call tests.
PiperOrigin-RevId: 224886231
Change-Id: I0fccb4d994601739d8b16b1d4e6b31f40297fb22
Diffstat (limited to 'test/syscalls/linux/timers.cc')
-rw-r--r-- | test/syscalls/linux/timers.cc | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/test/syscalls/linux/timers.cc b/test/syscalls/linux/timers.cc new file mode 100644 index 000000000..dfe231575 --- /dev/null +++ b/test/syscalls/linux/timers.cc @@ -0,0 +1,642 @@ +// 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 <signal.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <syscall.h> +#include <time.h> +#include <unistd.h> + +#include <atomic> + +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "test/util/cleanup.h" +#include "test/util/logging.h" +#include "test/util/multiprocess_util.h" +#include "test/util/posix_error.h" +#include "test/util/signal_util.h" +#include "test/util/test_util.h" +#include "test/util/thread_util.h" + +DEFINE_bool(timers_test_sleep, false, + "If true, sleep forever instead of running tests."); + +using ::testing::_; +using ::testing::AnyOf; + +namespace gvisor { +namespace testing { +namespace { + +#ifndef CPUCLOCK_PROF +#define CPUCLOCK_PROF 0 +#endif // CPUCLOCK_PROF + +PosixErrorOr<absl::Duration> ProcessCPUTime(pid_t pid) { + // Use pid-specific CPUCLOCK_PROF, which is the clock used to enforce + // RLIMIT_CPU. + clockid_t clockid = (~static_cast<clockid_t>(pid) << 3) | CPUCLOCK_PROF; + + struct timespec ts; + int ret = clock_gettime(clockid, &ts); + if (ret < 0) { + return PosixError(errno, "clock_gettime failed"); + } + + return absl::DurationFromTimespec(ts); +} + +void NoopSignalHandler(int signo) { + TEST_CHECK_MSG(SIGXCPU == signo, + "NoopSigHandler did not receive expected signal"); +} + +void UninstallingSignalHandler(int signo) { + TEST_CHECK_MSG(SIGXCPU == signo, + "UninstallingSignalHandler did not receive expected signal"); + struct sigaction rev_action; + rev_action.sa_handler = SIG_DFL; + rev_action.sa_flags = 0; + sigemptyset(&rev_action.sa_mask); + sigaction(SIGXCPU, &rev_action, nullptr); +} + +TEST(TimerTest, ProcessKilledOnCPUSoftLimit) { + constexpr absl::Duration kSoftLimit = absl::Seconds(1); + constexpr absl::Duration kHardLimit = absl::Seconds(3); + + struct rlimit cpu_limits; + cpu_limits.rlim_cur = absl::ToInt64Seconds(kSoftLimit); + cpu_limits.rlim_max = absl::ToInt64Seconds(kHardLimit); + + int pid = fork(); + MaybeSave(); + if (pid == 0) { + TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0); + MaybeSave(); + for (;;) { + } + } + auto c = Cleanup([pid] { + int status; + EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); + EXPECT_TRUE(WIFSIGNALED(status)); + EXPECT_EQ(WTERMSIG(status), SIGXCPU); + }); + + // Wait for the child to exit, but do not reap it. This will allow us to check + // its CPU usage while it is zombied. + EXPECT_THAT(waitid(P_PID, pid, nullptr, WEXITED | WNOWAIT), + SyscallSucceeds()); + + // Assert that the child spent 1s of CPU before getting killed. + // + // We must be careful to use CPUCLOCK_PROF, the same clock used for RLIMIT_CPU + // enforcement, to get correct results. Note that this is slightly different + // from rusage-reported CPU usage: + // + // RLIMIT_CPU, CPUCLOCK_PROF use kernel/sched/cputime.c:thread_group_cputime. + // rusage uses kernel/sched/cputime.c:thread_group_cputime_adjusted. + absl::Duration cpu = ASSERT_NO_ERRNO_AND_VALUE(ProcessCPUTime(pid)); + EXPECT_GE(cpu, kSoftLimit); + + // Child did not make it to the hard limit. + // + // Linux sends SIGXCPU synchronously with CPU tick updates. See + // kernel/time/timer.c:update_process_times: + // => account_process_tick // update task CPU usage. + // => run_posix_cpu_timers // enforce RLIMIT_CPU, sending signal. + // + // Thus, only chance for this to flake is if the system time required to + // deliver the signal exceeds 2s. + EXPECT_LT(cpu, kHardLimit); +} + +TEST(TimerTest, ProcessPingedRepeatedlyAfterCPUSoftLimit) { + struct sigaction new_action; + new_action.sa_handler = UninstallingSignalHandler; + new_action.sa_flags = 0; + sigemptyset(&new_action.sa_mask); + + constexpr absl::Duration kSoftLimit = absl::Seconds(1); + constexpr absl::Duration kHardLimit = absl::Seconds(10); + + struct rlimit cpu_limits; + cpu_limits.rlim_cur = absl::ToInt64Seconds(kSoftLimit); + cpu_limits.rlim_max = absl::ToInt64Seconds(kHardLimit); + + int pid = fork(); + MaybeSave(); + if (pid == 0) { + TEST_PCHECK(sigaction(SIGXCPU, &new_action, nullptr) == 0); + MaybeSave(); + TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0); + MaybeSave(); + for (;;) { + } + } + auto c = Cleanup([pid] { + int status; + EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); + EXPECT_TRUE(WIFSIGNALED(status)); + EXPECT_EQ(WTERMSIG(status), SIGXCPU); + }); + + // Wait for the child to exit, but do not reap it. This will allow us to check + // its CPU usage while it is zombied. + EXPECT_THAT(waitid(P_PID, pid, nullptr, WEXITED | WNOWAIT), + SyscallSucceeds()); + + absl::Duration cpu = ASSERT_NO_ERRNO_AND_VALUE(ProcessCPUTime(pid)); + // Following signals come every CPU second. + EXPECT_GE(cpu, kSoftLimit + absl::Seconds(1)); + + // Child did not make it to the hard limit. + // + // As above, should not flake. + EXPECT_LT(cpu, kHardLimit); +} + +TEST(TimerTest, ProcessKilledOnCPUHardLimit) { + struct sigaction new_action; + new_action.sa_handler = NoopSignalHandler; + new_action.sa_flags = 0; + sigemptyset(&new_action.sa_mask); + + constexpr absl::Duration kSoftLimit = absl::Seconds(1); + constexpr absl::Duration kHardLimit = absl::Seconds(3); + + struct rlimit cpu_limits; + cpu_limits.rlim_cur = absl::ToInt64Seconds(kSoftLimit); + cpu_limits.rlim_max = absl::ToInt64Seconds(kHardLimit); + + int pid = fork(); + MaybeSave(); + if (pid == 0) { + TEST_PCHECK(sigaction(SIGXCPU, &new_action, nullptr) == 0); + MaybeSave(); + TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0); + MaybeSave(); + for (;;) { + } + } + auto c = Cleanup([pid] { + int status; + EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); + EXPECT_TRUE(WIFSIGNALED(status)); + EXPECT_EQ(WTERMSIG(status), SIGKILL); + }); + + // Wait for the child to exit, but do not reap it. This will allow us to check + // its CPU usage while it is zombied. + EXPECT_THAT(waitid(P_PID, pid, nullptr, WEXITED | WNOWAIT), + SyscallSucceeds()); + + absl::Duration cpu = ASSERT_NO_ERRNO_AND_VALUE(ProcessCPUTime(pid)); + EXPECT_GE(cpu, kHardLimit); +} + +// RAII type for a kernel "POSIX" interval timer. (The kernel provides system +// calls such as timer_create that behave very similarly, but not identically, +// to those described by timer_create(2); in particular, the kernel does not +// implement SIGEV_THREAD. glibc builds POSIX-compliant interval timers based on +// these kernel interval timers.) +// +// Compare implementation to FileDescriptor. +class IntervalTimer { + public: + IntervalTimer() = default; + + explicit IntervalTimer(int id) { set_id(id); } + + IntervalTimer(IntervalTimer&& orig) : id_(orig.release()) {} + + IntervalTimer& operator=(IntervalTimer&& orig) { + if (this == &orig) return *this; + reset(orig.release()); + return *this; + } + + IntervalTimer(const IntervalTimer& other) = delete; + IntervalTimer& operator=(const IntervalTimer& other) = delete; + + ~IntervalTimer() { reset(); } + + int get() const { return id_; } + + int release() { + int const id = id_; + id_ = -1; + return id; + } + + void reset() { reset(-1); } + + void reset(int id) { + if (id_ >= 0) { + TEST_PCHECK(syscall(SYS_timer_delete, id_) == 0); + MaybeSave(); + } + set_id(id); + } + + PosixErrorOr<struct itimerspec> Set( + int flags, const struct itimerspec& new_value) const { + struct itimerspec old_value = {}; + if (syscall(SYS_timer_settime, id_, flags, &new_value, &old_value) < 0) { + return PosixError(errno, "timer_settime"); + } + MaybeSave(); + return old_value; + } + + PosixErrorOr<struct itimerspec> Get() const { + struct itimerspec curr_value = {}; + if (syscall(SYS_timer_gettime, id_, &curr_value) < 0) { + return PosixError(errno, "timer_gettime"); + } + MaybeSave(); + return curr_value; + } + + PosixErrorOr<int> Overruns() const { + int rv = syscall(SYS_timer_getoverrun, id_); + if (rv < 0) { + return PosixError(errno, "timer_getoverrun"); + } + MaybeSave(); + return rv; + } + + private: + void set_id(int id) { id_ = std::max(id, -1); } + + // Kernel timer_t is int; glibc timer_t is void*. + int id_ = -1; +}; + +PosixErrorOr<IntervalTimer> TimerCreate(clockid_t clockid, + const struct sigevent& sev) { + int timerid; + if (syscall(SYS_timer_create, clockid, &sev, &timerid) < 0) { + return PosixError(errno, "timer_create"); + } + MaybeSave(); + return IntervalTimer(timerid); +} + +// See timerfd.cc:TimerSlack() for rationale. +constexpr absl::Duration kTimerSlack = absl::Milliseconds(500); + +TEST(IntervalTimerTest, IsInitiallyStopped) { + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_NONE; + const auto timer = + ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); + const struct itimerspec its = ASSERT_NO_ERRNO_AND_VALUE(timer.Get()); + EXPECT_EQ(0, its.it_value.tv_sec); + EXPECT_EQ(0, its.it_value.tv_nsec); +} + +TEST(IntervalTimerTest, SingleShotSilent) { + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_NONE; + const auto timer = + ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); + + constexpr absl::Duration kDelay = absl::Seconds(1); + struct itimerspec its = {}; + its.it_value = absl::ToTimespec(kDelay); + ASSERT_NO_ERRNO(timer.Set(0, its)); + + // The timer should count down to 0 and stop since the interval is zero. No + // overruns should be counted. + absl::SleepFor(kDelay + kTimerSlack); + its = ASSERT_NO_ERRNO_AND_VALUE(timer.Get()); + EXPECT_EQ(0, its.it_value.tv_sec); + EXPECT_EQ(0, its.it_value.tv_nsec); + EXPECT_THAT(timer.Overruns(), IsPosixErrorOkAndHolds(0)); +} + +TEST(IntervalTimerTest, PeriodicSilent) { + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_NONE; + const auto timer = + ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); + + constexpr absl::Duration kPeriod = absl::Seconds(1); + struct itimerspec its = {}; + its.it_value = its.it_interval = absl::ToTimespec(kPeriod); + ASSERT_NO_ERRNO(timer.Set(0, its)); + + absl::SleepFor(kPeriod * 3 + kTimerSlack); + + // The timer should still be running. + its = ASSERT_NO_ERRNO_AND_VALUE(timer.Get()); + EXPECT_TRUE(its.it_value.tv_nsec != 0 || its.it_value.tv_sec != 0); + + // Timer expirations are not counted as overruns under SIGEV_NONE. + EXPECT_THAT(timer.Overruns(), IsPosixErrorOkAndHolds(0)); +} + +std::atomic<int> counted_signals; + +void IntervalTimerCountingSignalHandler(int sig, siginfo_t* info, + void* ucontext) { + counted_signals.fetch_add(1 + info->si_overrun); +} + +TEST(IntervalTimerTest, PeriodicGroupDirectedSignal) { + constexpr int kSigno = SIGUSR1; + constexpr int kSigvalue = 42; + + // Install our signal handler. + counted_signals.store(0); + struct sigaction sa = {}; + sa.sa_sigaction = IntervalTimerCountingSignalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + const auto scoped_sigaction = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kSigno, sa)); + + // Ensure that kSigno is unblocked on at least one thread. + const auto scoped_sigmask = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, kSigno)); + + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = kSigno; + sev.sigev_value.sival_int = kSigvalue; + auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); + + constexpr absl::Duration kPeriod = absl::Seconds(1); + constexpr int kCycles = 3; + struct itimerspec its = {}; + its.it_value = its.it_interval = absl::ToTimespec(kPeriod); + ASSERT_NO_ERRNO(timer.Set(0, its)); + + absl::SleepFor(kPeriod * kCycles + kTimerSlack); + EXPECT_GE(counted_signals.load(), kCycles); +} + +// From Linux's include/uapi/asm-generic/siginfo.h. +#ifndef sigev_notify_thread_id +#define sigev_notify_thread_id _sigev_un._tid +#endif + +TEST(IntervalTimerTest, PeriodicThreadDirectedSignal) { + constexpr int kSigno = SIGUSR1; + constexpr int kSigvalue = 42; + + // Block kSigno so that we can accumulate overruns. + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, kSigno); + const auto scoped_sigmask = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask)); + + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_signo = kSigno; + sev.sigev_value.sival_int = kSigvalue; + sev.sigev_notify_thread_id = gettid(); + auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); + + constexpr absl::Duration kPeriod = absl::Seconds(1); + constexpr int kCycles = 3; + struct itimerspec its = {}; + its.it_value = its.it_interval = absl::ToTimespec(kPeriod); + ASSERT_NO_ERRNO(timer.Set(0, its)); + absl::SleepFor(kPeriod * kCycles + kTimerSlack); + + // At least kCycles expirations should have occurred, resulting in kCycles-1 + // overruns (the first expiration sent the signal successfully). + siginfo_t si; + struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration()); + ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts), + SyscallSucceedsWithValue(kSigno)); + EXPECT_EQ(si.si_signo, kSigno); + EXPECT_EQ(si.si_code, SI_TIMER); + EXPECT_EQ(si.si_timerid, timer.get()); + EXPECT_GE(si.si_overrun, kCycles - 1); + EXPECT_EQ(si.si_int, kSigvalue); + + // Kill the timer, then drain any additional signal it may have enqueued. We + // can't do this before the preceding sigtimedwait because stopping or + // deleting the timer resets si_overrun to 0. + timer.reset(); + sigtimedwait(&mask, &si, &zero_ts); +} + +TEST(IntervalTimerTest, OtherThreadGroup) { + constexpr int kSigno = SIGUSR1; + + // Create a subprocess that does nothing until killed. + pid_t child_pid; + const auto sp = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( + "/proc/self/exe", ExecveArray({"timers", "--timers_test_sleep"}), + ExecveArray(), &child_pid, nullptr)); + + // Verify that we can't create a timer that would send signals to it. + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_signo = kSigno; + sev.sigev_notify_thread_id = child_pid; + EXPECT_THAT(TimerCreate(CLOCK_MONOTONIC, sev), PosixErrorIs(EINVAL, _)); +} + +TEST(IntervalTimerTest, RealTimeSignalsAreNotDuplicated) { + const int kSigno = SIGRTMIN; + constexpr int kSigvalue = 42; + + // Block signo so that we can accumulate overruns. + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, kSigno); + const auto scoped_sigmask = ScopedSignalMask(SIG_BLOCK, mask); + + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_signo = kSigno; + sev.sigev_value.sival_int = kSigvalue; + sev.sigev_notify_thread_id = gettid(); + const auto timer = + ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); + + constexpr absl::Duration kPeriod = absl::Seconds(1); + constexpr int kCycles = 3; + struct itimerspec its = {}; + its.it_value = its.it_interval = absl::ToTimespec(kPeriod); + ASSERT_NO_ERRNO(timer.Set(0, its)); + absl::SleepFor(kPeriod * kCycles + kTimerSlack); + + // Stop the timer so that no further signals are enqueued after sigtimedwait. + struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration()); + its.it_value = its.it_interval = zero_ts; + ASSERT_NO_ERRNO(timer.Set(0, its)); + + // The timer should have sent only a single signal, even though the kernel + // supports enqueueing of multiple RT signals. + siginfo_t si; + ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts), + SyscallSucceedsWithValue(kSigno)); + EXPECT_EQ(si.si_signo, kSigno); + EXPECT_EQ(si.si_code, SI_TIMER); + EXPECT_EQ(si.si_timerid, timer.get()); + // si_overrun was reset by timer_settime. + EXPECT_EQ(si.si_overrun, 0); + EXPECT_EQ(si.si_int, kSigvalue); + EXPECT_THAT(sigtimedwait(&mask, &si, &zero_ts), + SyscallFailsWithErrno(EAGAIN)); +} + +TEST(IntervalTimerTest, AlreadyPendingSignal) { + constexpr int kSigno = SIGUSR1; + constexpr int kSigvalue = 42; + + // Block kSigno so that we can accumulate overruns. + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, kSigno); + const auto scoped_sigmask = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask)); + + // Send ourselves a signal, preventing the timer from enqueuing. + ASSERT_THAT(tgkill(getpid(), gettid(), kSigno), SyscallSucceeds()); + + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_signo = kSigno; + sev.sigev_value.sival_int = kSigvalue; + sev.sigev_notify_thread_id = gettid(); + auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); + + constexpr absl::Duration kPeriod = absl::Seconds(1); + constexpr int kCycles = 3; + struct itimerspec its = {}; + its.it_value = its.it_interval = absl::ToTimespec(kPeriod); + ASSERT_NO_ERRNO(timer.Set(0, its)); + + // End the sleep one cycle short; we will sleep for one more cycle below. + absl::SleepFor(kPeriod * (kCycles - 1)); + + // Dequeue the first signal, which we sent to ourselves with tgkill. + siginfo_t si; + struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration()); + ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts), + SyscallSucceedsWithValue(kSigno)); + EXPECT_EQ(si.si_signo, kSigno); + // glibc sigtimedwait silently replaces SI_TKILL with SI_USER: + // sysdeps/unix/sysv/linux/sigtimedwait.c:__sigtimedwait(). This isn't + // documented, so we don't depend on it. + EXPECT_THAT(si.si_code, AnyOf(SI_USER, SI_TKILL)); + + // Sleep for 1 more cycle to give the timer time to send a signal. + absl::SleepFor(kPeriod + kTimerSlack); + + // At least kCycles expirations should have occurred, resulting in kCycles-1 + // overruns (the last expiration sent the signal successfully). + ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts), + SyscallSucceedsWithValue(kSigno)); + EXPECT_EQ(si.si_signo, kSigno); + EXPECT_EQ(si.si_code, SI_TIMER); + EXPECT_EQ(si.si_timerid, timer.get()); + EXPECT_GE(si.si_overrun, kCycles - 1); + EXPECT_EQ(si.si_int, kSigvalue); + + // Kill the timer, then drain any additional signal it may have enqueued. We + // can't do this before the preceding sigtimedwait because stopping or + // deleting the timer resets si_overrun to 0. + timer.reset(); + sigtimedwait(&mask, &si, &zero_ts); +} + +TEST(IntervalTimerTest, IgnoredSignalCountsAsOverrun) { + constexpr int kSigno = SIGUSR1; + constexpr int kSigvalue = 42; + + // Ignore kSigno. + struct sigaction sa = {}; + sa.sa_handler = SIG_IGN; + const auto scoped_sigaction = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kSigno, sa)); + + // Unblock kSigno so that ignored signals will be discarded. + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, kSigno); + auto scoped_sigmask = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, mask)); + + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_signo = kSigno; + sev.sigev_value.sival_int = kSigvalue; + sev.sigev_notify_thread_id = gettid(); + auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); + + constexpr absl::Duration kPeriod = absl::Seconds(1); + constexpr int kCycles = 3; + struct itimerspec its = {}; + its.it_value = its.it_interval = absl::ToTimespec(kPeriod); + ASSERT_NO_ERRNO(timer.Set(0, its)); + + // End the sleep one cycle short; we will sleep for one more cycle below. + absl::SleepFor(kPeriod * (kCycles - 1)); + + // Block kSigno so that ignored signals will be enqueued. + scoped_sigmask.Release()(); + scoped_sigmask = ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask)); + + // Sleep for 1 more cycle to give the timer time to send a signal. + absl::SleepFor(kPeriod + kTimerSlack); + + // At least kCycles expirations should have occurred, resulting in kCycles-1 + // overruns (the last expiration sent the signal successfully). + siginfo_t si; + struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration()); + ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts), + SyscallSucceedsWithValue(kSigno)); + EXPECT_EQ(si.si_signo, kSigno); + EXPECT_EQ(si.si_code, SI_TIMER); + EXPECT_EQ(si.si_timerid, timer.get()); + EXPECT_GE(si.si_overrun, kCycles - 1); + EXPECT_EQ(si.si_int, kSigvalue); + + // Kill the timer, then drain any additional signal it may have enqueued. We + // can't do this before the preceding sigtimedwait because stopping or + // deleting the timer resets si_overrun to 0. + timer.reset(); + sigtimedwait(&mask, &si, &zero_ts); +} + +} // namespace +} // namespace testing +} // namespace gvisor + +int main(int argc, char** argv) { + gvisor::testing::TestInit(&argc, &argv); + + if (FLAGS_timers_test_sleep) { + while (true) { + absl::SleepFor(absl::Seconds(10)); + } + } + + return RUN_ALL_TESTS(); +} |