diff options
Diffstat (limited to 'test/syscalls/linux/concurrency.cc')
-rw-r--r-- | test/syscalls/linux/concurrency.cc | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/test/syscalls/linux/concurrency.cc b/test/syscalls/linux/concurrency.cc new file mode 100644 index 000000000..2c13b315c --- /dev/null +++ b/test/syscalls/linux/concurrency.cc @@ -0,0 +1,124 @@ +// 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 <atomic> + +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "test/util/test_util.h" +#include "test/util/thread_util.h" + +namespace gvisor { +namespace testing { +namespace { + +// Test that a thread that never yields to the OS does not prevent other threads +// from running. +TEST(ConcurrencyTest, SingleProcessMultithreaded) { + std::atomic<int> a(0); + + ScopedThread t([&a]() { + while (!a.load()) { + } + }); + + absl::SleepFor(absl::Seconds(1)); + + // We are still able to execute code in this thread. The other hasn't + // permanently hung execution in both threads. + a.store(1); +} + +// Test that multiple threads in this process continue to execute in parallel, +// even if an unrelated second process is spawned. +TEST(ConcurrencyTest, MultiProcessMultithreaded) { + // In PID 1, start TIDs 1 and 2, and put both to sleep. + // + // Start PID 3, which spins for 5 seconds, then exits. + // + // TIDs 1 and 2 wake and attempt to Activate, which cannot occur until PID 3 + // exits. + // + // Both TIDs 1 and 2 should be woken. If they are not both woken, the test + // hangs. + // + // This is all fundamentally racy. If we are failing to wake all threads, the + // expectation is that this test becomes flaky, rather than consistently + // failing. + // + // If additional background threads fail to block, we may never schedule the + // child, at which point this test effectively becomes + // MultiProcessConcurrency. That's not expected to occur. + + std::atomic<int> a(0); + ScopedThread t([&a]() { + // Block so that PID 3 can execute and we can wait on its exit. + absl::SleepFor(absl::Seconds(1)); + while (!a.load()) { + } + }); + + pid_t child_pid; + ASSERT_THAT(child_pid = fork(), SyscallSucceeds()); + if (child_pid == 0) { + // Busy wait without making any blocking syscalls. + auto end = absl::Now() + absl::Seconds(5); + while (absl::Now() < end) { + } + _exit(0); + } + + absl::SleepFor(absl::Seconds(1)); + + // If only TID 1 is woken, thread.Join will hang. + // If only TID 2 is woken, both will hang. + a.store(1); + t.Join(); + + int status = 0; + EXPECT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); + EXPECT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), 0); +} + +// Test that multiple processes can execute concurrently, even if one process +// never yields. +TEST(ConcurrencyTest, MultiProcessConcurrency) { + + pid_t child_pid; + ASSERT_THAT(child_pid = fork(), SyscallSucceeds()); + if (child_pid == 0) { + while (true) { + } + __builtin_unreachable(); + } + + absl::SleepFor(absl::Seconds(5)); + + // We are still able to execute code in this process. The other hasn't + // permanently hung execution in both processes. + ASSERT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); + int status = 0; + + ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(WTERMSIG(status), SIGKILL); +} + +} // namespace +} // namespace testing +} // namespace gvisor |