summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/itimer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/itimer.cc')
-rw-r--r--test/syscalls/linux/itimer.cc342
1 files changed, 342 insertions, 0 deletions
diff --git a/test/syscalls/linux/itimer.cc b/test/syscalls/linux/itimer.cc
new file mode 100644
index 000000000..ee5871cbe
--- /dev/null
+++ b/test/syscalls/linux/itimer.cc
@@ -0,0 +1,342 @@
+// 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 <signal.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include <atomic>
+#include <functional>
+#include <iostream>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "test/util/file_descriptor.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"
+#include "test/util/timer_util.h"
+
+namespace gvisor {
+namespace testing {
+namespace {
+
+constexpr char kSIGALRMToMainThread[] = "--itimer_sigarlm_to_main_thread";
+constexpr char kSIGPROFFairnessActive[] = "--itimer_sigprof_fairness_active";
+constexpr char kSIGPROFFairnessIdle[] = "--itimer_sigprof_fairness_idle";
+
+// Time period to be set for the itimers.
+constexpr absl::Duration kPeriod = absl::Milliseconds(25);
+// Total amount of time to spend per thread.
+constexpr absl::Duration kTestDuration = absl::Seconds(20);
+// Amount of spin iterations to perform as the minimum work item per thread.
+// Chosen to be sub-millisecond range.
+constexpr int kIterations = 10000000;
+// Allow deviation in the number of samples.
+constexpr double kNumSamplesDeviationRatio = 0.2;
+constexpr double kNumSamplesMinRatio = 0.5;
+
+TEST(ItimerTest, ItimervalUpdatedBeforeExpiration) {
+ constexpr int kSleepSecs = 10;
+ constexpr int kAlarmSecs = 15;
+ static_assert(
+ kSleepSecs < kAlarmSecs,
+ "kSleepSecs must be less than kAlarmSecs for the test to be meaningful");
+ constexpr int kMaxRemainingSecs = kAlarmSecs - kSleepSecs;
+
+ // Install a no-op handler for SIGALRM.
+ struct sigaction sa = {};
+ sigfillset(&sa.sa_mask);
+ sa.sa_handler = +[](int signo) {};
+ auto const cleanup_sa =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
+
+ // Set an itimer-based alarm for kAlarmSecs from now.
+ struct itimerval itv = {};
+ itv.it_value.tv_sec = kAlarmSecs;
+ auto const cleanup_itimer =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_REAL, itv));
+
+ // After sleeping for kSleepSecs, the itimer value should reflect the elapsed
+ // time even if it hasn't expired.
+ absl::SleepFor(absl::Seconds(kSleepSecs));
+ ASSERT_THAT(getitimer(ITIMER_REAL, &itv), SyscallSucceeds());
+ EXPECT_TRUE(
+ itv.it_value.tv_sec < kMaxRemainingSecs ||
+ (itv.it_value.tv_sec == kMaxRemainingSecs && itv.it_value.tv_usec == 0))
+ << "Remaining time: " << itv.it_value.tv_sec << " seconds + "
+ << itv.it_value.tv_usec << " microseconds";
+}
+
+ABSL_CONST_INIT static thread_local std::atomic_int signal_test_num_samples =
+ ATOMIC_VAR_INIT(0);
+
+void SignalTestSignalHandler(int /*signum*/) { signal_test_num_samples++; }
+
+struct SignalTestResult {
+ int expected_total;
+ int main_thread_samples;
+ std::vector<int> worker_samples;
+};
+
+std::ostream& operator<<(std::ostream& os, const SignalTestResult& r) {
+ os << "{expected_total: " << r.expected_total
+ << ", main_thread_samples: " << r.main_thread_samples
+ << ", worker_samples: [";
+ bool first = true;
+ for (int sample : r.worker_samples) {
+ if (!first) {
+ os << ", ";
+ }
+ os << sample;
+ first = false;
+ }
+ os << "]}";
+ return os;
+}
+
+// Starts two worker threads and itimer id and measures the number of signal
+// delivered to each thread.
+SignalTestResult ItimerSignalTest(int id, clock_t main_clock,
+ clock_t worker_clock, int signal,
+ absl::Duration sleep) {
+ signal_test_num_samples = 0;
+
+ struct sigaction sa = {};
+ sa.sa_handler = &SignalTestSignalHandler;
+ sa.sa_flags = SA_RESTART;
+ sigemptyset(&sa.sa_mask);
+ auto sigaction_cleanup = std::move(ScopedSigaction(signal, sa).ValueOrDie());
+
+ int socketfds[2];
+ TEST_PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, socketfds) == 0);
+
+ // Do the spinning in the workers.
+ std::function<void*(int)> work = [&](int socket_fd) {
+ FileDescriptor fd(socket_fd);
+
+ absl::Time finish = Now(worker_clock) + kTestDuration;
+ while (Now(worker_clock) < finish) {
+ // Blocked on read.
+ char c;
+ RetryEINTR(read)(fd.get(), &c, 1);
+ for (int i = 0; i < kIterations; i++) {
+ // Ensure compiler won't optimize this loop away.
+ asm("");
+ }
+
+ if (sleep != absl::ZeroDuration()) {
+ // Sleep so that the entire process is idle for a while.
+ absl::SleepFor(sleep);
+ }
+
+ // Unblock the other thread.
+ RetryEINTR(write)(fd.get(), &c, 1);
+ }
+
+ return reinterpret_cast<void*>(signal_test_num_samples.load());
+ };
+
+ ScopedThread th1(
+ static_cast<std::function<void*()>>(std::bind(work, socketfds[0])));
+ ScopedThread th2(
+ static_cast<std::function<void*()>>(std::bind(work, socketfds[1])));
+
+ absl::Time start = Now(main_clock);
+ // Start the timer.
+ struct itimerval timer = {};
+ timer.it_value = absl::ToTimeval(kPeriod);
+ timer.it_interval = absl::ToTimeval(kPeriod);
+ auto cleanup_itimer = std::move(ScopedItimer(id, timer).ValueOrDie());
+
+ // Unblock th1.
+ //
+ // N.B. th2 owns socketfds[1] but can't close it until it unblocks.
+ char c = 0;
+ TEST_CHECK(write(socketfds[1], &c, 1) == 1);
+
+ SignalTestResult result;
+
+ // Wait for the workers to be done and collect their sample counts.
+ result.worker_samples.push_back(reinterpret_cast<int64_t>(th1.Join()));
+ result.worker_samples.push_back(reinterpret_cast<int64_t>(th2.Join()));
+ cleanup_itimer.Release()();
+ result.expected_total = (Now(main_clock) - start) / kPeriod;
+ result.main_thread_samples = signal_test_num_samples.load();
+
+ return result;
+}
+
+int TestSIGALRMToMainThread() {
+ SignalTestResult result =
+ ItimerSignalTest(ITIMER_REAL, CLOCK_REALTIME, CLOCK_REALTIME, SIGALRM,
+ absl::ZeroDuration());
+
+ std::cerr << "result: " << result << std::endl;
+
+ // ITIMER_REAL-generated SIGALRMs prefer to deliver to the thread group leader
+ // (but don't guarantee it), so we expect to see most samples on the main
+ // thread.
+ //
+ // Linux only guarantees timers will never expire before the requested time.
+ // Thus, we only check the upper bound and also it at least have one sample.
+ TEST_CHECK(result.main_thread_samples <= result.expected_total);
+ TEST_CHECK(result.main_thread_samples > 0);
+ for (int num : result.worker_samples) {
+ TEST_CHECK_MSG(num <= 50, "worker received too many samples");
+ }
+
+ return 0;
+}
+
+// Random save/restore is disabled as it introduces additional latency and
+// unpredictable distribution patterns.
+TEST(ItimerTest, DeliversSIGALRMToMainThread_NoRandomSave) {
+ pid_t child;
+ int execve_errno;
+ auto kill = ASSERT_NO_ERRNO_AND_VALUE(
+ ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGALRMToMainThread},
+ {}, &child, &execve_errno));
+ EXPECT_EQ(0, execve_errno);
+
+ int status;
+ EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0),
+ SyscallSucceedsWithValue(child));
+
+ // Not required anymore.
+ kill.Release();
+
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status;
+}
+
+// Signals are delivered to threads fairly.
+//
+// sleep indicates how long to sleep worker threads each iteration to make the
+// entire process idle.
+int TestSIGPROFFairness(absl::Duration sleep) {
+ SignalTestResult result =
+ ItimerSignalTest(ITIMER_PROF, CLOCK_PROCESS_CPUTIME_ID,
+ CLOCK_THREAD_CPUTIME_ID, SIGPROF, sleep);
+
+ std::cerr << "result: " << result << std::endl;
+
+ // The number of samples on the main thread should be very low as it did
+ // nothing.
+ TEST_CHECK(result.main_thread_samples < 60);
+
+ // Both workers should get roughly equal number of samples.
+ TEST_CHECK(result.worker_samples.size() == 2);
+
+ TEST_CHECK(result.expected_total > 0);
+
+ // In an ideal world each thread would get exactly 50% of the signals,
+ // but since that's unlikely to happen we allow for them to get no less than
+ // kNumSamplesDeviationRatio of the total observed samples.
+ TEST_CHECK_MSG(std::abs(result.worker_samples[0] - result.worker_samples[1]) <
+ ((result.worker_samples[0] + result.worker_samples[1]) *
+ kNumSamplesDeviationRatio),
+ "one worker received disproportionate share of samples");
+
+ return 0;
+}
+
+// Random save/restore is disabled as it introduces additional latency and
+// unpredictable distribution patterns.
+TEST(ItimerTest, DeliversSIGPROFToThreadsRoughlyFairlyActive_NoRandomSave) {
+ pid_t child;
+ int execve_errno;
+ auto kill = ASSERT_NO_ERRNO_AND_VALUE(
+ ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGPROFFairnessActive},
+ {}, &child, &execve_errno));
+ EXPECT_EQ(0, execve_errno);
+
+ int status;
+ EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0),
+ SyscallSucceedsWithValue(child));
+
+ // Not required anymore.
+ kill.Release();
+
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << "Exited with code: " << status;
+}
+
+// Random save/restore is disabled as it introduces additional latency and
+// unpredictable distribution patterns.
+TEST(ItimerTest, DeliversSIGPROFToThreadsRoughlyFairlyIdle_NoRandomSave) {
+ pid_t child;
+ int execve_errno;
+ auto kill = ASSERT_NO_ERRNO_AND_VALUE(
+ ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGPROFFairnessIdle},
+ {}, &child, &execve_errno));
+ EXPECT_EQ(0, execve_errno);
+
+ int status;
+ EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0),
+ SyscallSucceedsWithValue(child));
+
+ // Not required anymore.
+ kill.Release();
+
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << "Exited with code: " << status;
+}
+
+} // namespace
+} // namespace testing
+} // namespace gvisor
+
+namespace {
+void MaskSIGPIPE() {
+ // Always mask SIGPIPE as it's common and tests aren't expected to handle it.
+ // We don't take the TestInit() path so we must do this manually.
+ struct sigaction sa = {};
+ sa.sa_handler = SIG_IGN;
+ TEST_CHECK(sigaction(SIGPIPE, &sa, nullptr) == 0);
+}
+} // namespace
+
+int main(int argc, char** argv) {
+ // These tests require no background threads, so check for them before
+ // TestInit.
+ for (int i = 0; i < argc; i++) {
+ absl::string_view arg(argv[i]);
+
+ if (arg == gvisor::testing::kSIGALRMToMainThread) {
+ MaskSIGPIPE();
+ return gvisor::testing::TestSIGALRMToMainThread();
+ }
+ if (arg == gvisor::testing::kSIGPROFFairnessActive) {
+ MaskSIGPIPE();
+ return gvisor::testing::TestSIGPROFFairness(absl::ZeroDuration());
+ }
+ if (arg == gvisor::testing::kSIGPROFFairnessIdle) {
+ MaskSIGPIPE();
+ return gvisor::testing::TestSIGPROFFairness(absl::Milliseconds(10));
+ }
+ }
+
+ gvisor::testing::TestInit(&argc, &argv);
+
+ return RUN_ALL_TESTS();
+}