diff options
Diffstat (limited to 'test/syscalls/linux/futex.cc')
-rw-r--r-- | test/syscalls/linux/futex.cc | 834 |
1 files changed, 0 insertions, 834 deletions
diff --git a/test/syscalls/linux/futex.cc b/test/syscalls/linux/futex.cc deleted file mode 100644 index 90b1f0508..000000000 --- a/test/syscalls/linux/futex.cc +++ /dev/null @@ -1,834 +0,0 @@ -// 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 <linux/futex.h> -#include <linux/types.h> -#include <sys/syscall.h> -#include <sys/time.h> -#include <sys/types.h> -#include <syscall.h> -#include <unistd.h> - -#include <algorithm> -#include <atomic> -#include <memory> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/memory/memory.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/memory_util.h" -#include "test/util/save_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/time_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Amount of time we wait for threads doing futex_wait to start running before -// doing futex_wake. -constexpr auto kWaiterStartupDelay = absl::Seconds(3); - -// Default timeout for waiters in tests where we expect a futex_wake to be -// ineffective. -constexpr auto kIneffectiveWakeTimeout = absl::Seconds(6); - -static_assert(kWaiterStartupDelay < kIneffectiveWakeTimeout, - "futex_wait will time out before futex_wake is called"); - -int futex_wait(bool priv, std::atomic<int>* uaddr, int val, - absl::Duration timeout = absl::InfiniteDuration()) { - int op = FUTEX_WAIT; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - - if (timeout == absl::InfiniteDuration()) { - return RetryEINTR(syscall)(SYS_futex, uaddr, op, val, nullptr); - } - - // FUTEX_WAIT doesn't adjust the timeout if it returns EINTR, so we have to do - // so. - while (true) { - auto const timeout_ts = absl::ToTimespec(timeout); - MonotonicTimer timer; - timer.Start(); - int const ret = syscall(SYS_futex, uaddr, op, val, &timeout_ts); - if (ret != -1 || errno != EINTR) { - return ret; - } - timeout = std::max(timeout - timer.Duration(), absl::ZeroDuration()); - } -} - -int futex_wait_bitset(bool priv, std::atomic<int>* uaddr, int val, int bitset, - absl::Time deadline = absl::InfiniteFuture()) { - int op = FUTEX_WAIT_BITSET | FUTEX_CLOCK_REALTIME; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - - auto const deadline_ts = absl::ToTimespec(deadline); - return RetryEINTR(syscall)( - SYS_futex, uaddr, op, val, - deadline == absl::InfiniteFuture() ? nullptr : &deadline_ts, nullptr, - bitset); -} - -int futex_wake(bool priv, std::atomic<int>* uaddr, int count) { - int op = FUTEX_WAKE; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - return syscall(SYS_futex, uaddr, op, count); -} - -int futex_wake_bitset(bool priv, std::atomic<int>* uaddr, int count, - int bitset) { - int op = FUTEX_WAKE_BITSET; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - return syscall(SYS_futex, uaddr, op, count, nullptr, nullptr, bitset); -} - -int futex_wake_op(bool priv, std::atomic<int>* uaddr1, std::atomic<int>* uaddr2, - int nwake1, int nwake2, uint32_t sub_op) { - int op = FUTEX_WAKE_OP; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - return syscall(SYS_futex, uaddr1, op, nwake1, nwake2, uaddr2, sub_op); -} - -int futex_lock_pi(bool priv, std::atomic<int>* uaddr) { - int op = FUTEX_LOCK_PI; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - int zero = 0; - if (uaddr->compare_exchange_strong(zero, gettid())) { - return 0; - } - return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr); -} - -int futex_trylock_pi(bool priv, std::atomic<int>* uaddr) { - int op = FUTEX_TRYLOCK_PI; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - int zero = 0; - if (uaddr->compare_exchange_strong(zero, gettid())) { - return 0; - } - return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr); -} - -int futex_unlock_pi(bool priv, std::atomic<int>* uaddr) { - int op = FUTEX_UNLOCK_PI; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - int tid = gettid(); - if (uaddr->compare_exchange_strong(tid, 0)) { - return 0; - } - return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr); -} - -// Fixture for futex tests parameterized by whether to use private or shared -// futexes. -class PrivateAndSharedFutexTest : public ::testing::TestWithParam<bool> { - protected: - bool IsPrivate() const { return GetParam(); } - int PrivateFlag() const { return IsPrivate() ? FUTEX_PRIVATE_FLAG : 0; } -}; - -// FUTEX_WAIT with 0 timeout does not block. -TEST_P(PrivateAndSharedFutexTest, Wait_ZeroTimeout) { - struct timespec timeout = {}; - - // Don't use the futex_wait helper because it adjusts timeout. - int a = 1; - EXPECT_THAT(syscall(SYS_futex, &a, FUTEX_WAIT | PrivateFlag(), a, &timeout), - SyscallFailsWithErrno(ETIMEDOUT)); -} - -TEST_P(PrivateAndSharedFutexTest, Wait_Timeout) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - - MonotonicTimer timer; - timer.Start(); - constexpr absl::Duration kTimeout = absl::Seconds(1); - EXPECT_THAT(futex_wait(IsPrivate(), &a, a, kTimeout), - SyscallFailsWithErrno(ETIMEDOUT)); - EXPECT_GE(timer.Duration(), kTimeout); -} - -TEST_P(PrivateAndSharedFutexTest, Wait_BitsetTimeout) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - - MonotonicTimer timer; - timer.Start(); - constexpr absl::Duration kTimeout = absl::Seconds(1); - EXPECT_THAT( - futex_wait_bitset(IsPrivate(), &a, a, 0xffffffff, absl::Now() + kTimeout), - SyscallFailsWithErrno(ETIMEDOUT)); - EXPECT_GE(timer.Duration(), kTimeout); -} - -TEST_P(PrivateAndSharedFutexTest, WaitBitset_NegativeTimeout) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - - MonotonicTimer timer; - timer.Start(); - EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, a, 0xffffffff, - absl::Now() - absl::Seconds(1)), - SyscallFailsWithErrno(ETIMEDOUT)); -} - -TEST_P(PrivateAndSharedFutexTest, Wait_WrongVal) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - EXPECT_THAT(futex_wait(IsPrivate(), &a, a + 1), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(PrivateAndSharedFutexTest, Wait_ZeroBitset) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, a, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(PrivateAndSharedFutexTest, Wake1_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - // Prevent save/restore from interrupting futex_wait, which will cause it to - // return EAGAIN instead of the expected result if futex_wait is restarted - // after we change the value of a below. - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), - SyscallSucceedsWithValue(0)); - }); - absl::SleepFor(kWaiterStartupDelay); - - // Change a so that if futex_wake happens before futex_wait, the latter - // returns EAGAIN instead of hanging the test. - a.fetch_add(1); - EXPECT_THAT(futex_wake(IsPrivate(), &a, 1), SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, Wake0_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - // Prevent save/restore from interrupting futex_wait, which will cause it to - // return EAGAIN instead of the expected result if futex_wait is restarted - // after we change the value of a below. - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), - SyscallSucceedsWithValue(0)); - }); - absl::SleepFor(kWaiterStartupDelay); - - // Change a so that if futex_wake happens before futex_wait, the latter - // returns EAGAIN instead of hanging the test. - a.fetch_add(1); - // The Linux kernel wakes one waiter even if val is 0 or negative. - EXPECT_THAT(futex_wake(IsPrivate(), &a, 0), SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, WakeAll_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - constexpr int kThreads = 5; - std::vector<std::unique_ptr<ScopedThread>> threads; - threads.reserve(kThreads); - for (int i = 0; i < kThreads; i++) { - threads.push_back(absl::make_unique<ScopedThread>([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), - SyscallSucceeds()); - })); - } - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake(IsPrivate(), &a, kThreads), - SyscallSucceedsWithValue(kThreads)); -} - -TEST_P(PrivateAndSharedFutexTest, WakeSome_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - constexpr int kThreads = 5; - constexpr int kWokenThreads = 3; - static_assert(kWokenThreads < kThreads, - "can't wake more threads than are created"); - std::vector<std::unique_ptr<ScopedThread>> threads; - threads.reserve(kThreads); - std::vector<int> rets; - rets.reserve(kThreads); - std::vector<int> errs; - errs.reserve(kThreads); - for (int i = 0; i < kThreads; i++) { - rets.push_back(-1); - errs.push_back(0); - } - for (int i = 0; i < kThreads; i++) { - threads.push_back(absl::make_unique<ScopedThread>([&, i] { - rets[i] = - futex_wait(IsPrivate(), &a, kInitialValue, kIneffectiveWakeTimeout); - errs[i] = errno; - })); - } - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake(IsPrivate(), &a, kWokenThreads), - SyscallSucceedsWithValue(kWokenThreads)); - - int woken = 0; - int timedout = 0; - for (int i = 0; i < kThreads; i++) { - threads[i]->Join(); - if (rets[i] == 0) { - woken++; - } else if (errs[i] == ETIMEDOUT) { - timedout++; - } else { - ADD_FAILURE() << " thread " << i << ": returned " << rets[i] << ", errno " - << errs[i]; - } - } - EXPECT_EQ(woken, kWokenThreads); - EXPECT_EQ(timedout, kThreads - kWokenThreads); -} - -TEST_P(PrivateAndSharedFutexTest, WaitBitset_Wake_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, kInitialValue, 0b01001000), - SyscallSucceeds()); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake(IsPrivate(), &a, 1), SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, Wait_WakeBitset_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), SyscallSucceeds()); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake_bitset(IsPrivate(), &a, 1, 0b01001000), - SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, WaitBitset_WakeBitsetMatch_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - constexpr int kBitset = 0b01001000; - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, kInitialValue, kBitset), - SyscallSucceeds()); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake_bitset(IsPrivate(), &a, 1, kBitset), - SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, WaitBitset_WakeBitsetNoMatch_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - constexpr int kWaitBitset = 0b01000001; - constexpr int kWakeBitset = 0b00101000; - static_assert((kWaitBitset & kWakeBitset) == 0, - "futex_wake_bitset will wake waiter"); - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, kInitialValue, kWaitBitset, - absl::Now() + kIneffectiveWakeTimeout), - SyscallFailsWithErrno(ETIMEDOUT)); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake_bitset(IsPrivate(), &a, 1, kWakeBitset), - SyscallSucceedsWithValue(0)); -} - -TEST_P(PrivateAndSharedFutexTest, WakeOpCondSuccess_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - std::atomic<int> b = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - ScopedThread thread_a([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), SyscallSucceeds()); - }); - ScopedThread thread_b([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &b, kInitialValue), SyscallSucceeds()); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - b.fetch_add(1); - // This futex_wake_op should: - // - Wake 1 waiter on a unconditionally. - // - Wake 1 waiter on b if b == kInitialValue + 1, which it is. - // - Do "b += 1". - EXPECT_THAT(futex_wake_op(IsPrivate(), &a, &b, 1, 1, - FUTEX_OP(FUTEX_OP_ADD, 1, FUTEX_OP_CMP_EQ, - (kInitialValue + 1))), - SyscallSucceedsWithValue(2)); - EXPECT_EQ(b, kInitialValue + 2); -} - -TEST_P(PrivateAndSharedFutexTest, WakeOpCondFailure_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - std::atomic<int> b = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - ScopedThread thread_a([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), SyscallSucceeds()); - }); - ScopedThread thread_b([&] { - EXPECT_THAT( - futex_wait(IsPrivate(), &b, kInitialValue, kIneffectiveWakeTimeout), - SyscallFailsWithErrno(ETIMEDOUT)); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - b.fetch_add(1); - // This futex_wake_op should: - // - Wake 1 waiter on a unconditionally. - // - Wake 1 waiter on b if b == kInitialValue - 1, which it isn't. - // - Do "b += 1". - EXPECT_THAT(futex_wake_op(IsPrivate(), &a, &b, 1, 1, - FUTEX_OP(FUTEX_OP_ADD, 1, FUTEX_OP_CMP_EQ, - (kInitialValue - 1))), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(b, kInitialValue + 2); -} - -TEST_P(PrivateAndSharedFutexTest, NoWakeInterprocessPrivateAnon_NoRandomSave) { - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr()); - constexpr int kInitialValue = 1; - ptr->store(kInitialValue); - - DisableSave ds; - pid_t const child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(futex_wait(IsPrivate(), ptr, kInitialValue, - kIneffectiveWakeTimeout) == -1 && - errno == ETIMEDOUT); - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - absl::SleepFor(kWaiterStartupDelay); - - EXPECT_THAT(futex_wake(IsPrivate(), ptr, 1), SyscallSucceedsWithValue(0)); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST_P(PrivateAndSharedFutexTest, WakeAfterCOWBreak_NoRandomSave) { - // Use a futex on a non-stack mapping so we can be sure that the child process - // below isn't the one that breaks copy-on-write. - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr()); - constexpr int kInitialValue = 1; - ptr->store(kInitialValue); - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait(IsPrivate(), ptr, kInitialValue), SyscallSucceeds()); - }); - absl::SleepFor(kWaiterStartupDelay); - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // Wait to be killed by the parent. - while (true) pause(); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - auto cleanup_child = Cleanup([&] { - EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; - }); - - // In addition to preventing a late futex_wait from sleeping, this breaks - // copy-on-write on the mapped page. - ptr->fetch_add(1); - EXPECT_THAT(futex_wake(IsPrivate(), ptr, 1), SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, WakeWrongKind_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT( - futex_wait(IsPrivate(), &a, kInitialValue, kIneffectiveWakeTimeout), - SyscallFailsWithErrno(ETIMEDOUT)); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - // The value of priv passed to futex_wake is the opposite of that passed to - // the futex_waiter; we expect this not to wake the waiter. - EXPECT_THAT(futex_wake(!IsPrivate(), &a, 1), SyscallSucceedsWithValue(0)); -} - -INSTANTIATE_TEST_SUITE_P(SharedPrivate, PrivateAndSharedFutexTest, - ::testing::Bool()); - -// Passing null as the address only works for private futexes. - -TEST(PrivateFutexTest, WakeOp0Set) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - - int futex_op = FUTEX_OP(FUTEX_OP_SET, 2, 0, 0); - EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(a, 2); -} - -TEST(PrivateFutexTest, WakeOp0Add) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - int futex_op = FUTEX_OP(FUTEX_OP_ADD, 1, 0, 0); - EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(a, 2); -} - -TEST(PrivateFutexTest, WakeOp0Or) { - std::atomic<int> a = ATOMIC_VAR_INIT(0b01); - int futex_op = FUTEX_OP(FUTEX_OP_OR, 0b10, 0, 0); - EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(a, 0b11); -} - -TEST(PrivateFutexTest, WakeOp0Andn) { - std::atomic<int> a = ATOMIC_VAR_INIT(0b11); - int futex_op = FUTEX_OP(FUTEX_OP_ANDN, 0b10, 0, 0); - EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(a, 0b01); -} - -TEST(PrivateFutexTest, WakeOp0Xor) { - std::atomic<int> a = ATOMIC_VAR_INIT(0b1010); - int futex_op = FUTEX_OP(FUTEX_OP_XOR, 0b1100, 0, 0); - EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(a, 0b0110); -} - -TEST(SharedFutexTest, WakeInterprocessSharedAnon_NoRandomSave) { - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED)); - auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr()); - constexpr int kInitialValue = 1; - ptr->store(kInitialValue); - - DisableSave ds; - pid_t const child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(futex_wait(false, ptr, kInitialValue) == 0); - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - auto kill_child = Cleanup( - [&] { EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); }); - absl::SleepFor(kWaiterStartupDelay); - - ptr->fetch_add(1); - // This is an ASSERT so that if it fails, we immediately abort the test (and - // kill the subprocess). - ASSERT_THAT(futex_wake(false, ptr, 1), SyscallSucceedsWithValue(1)); - - kill_child.Release(); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST(SharedFutexTest, WakeInterprocessFile_NoRandomSave) { - auto const file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - ASSERT_THAT(truncate(file.path().c_str(), kPageSize), SyscallSucceeds()); - auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); - auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr()); - constexpr int kInitialValue = 1; - ptr->store(kInitialValue); - - DisableSave ds; - pid_t const child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(futex_wait(false, ptr, kInitialValue) == 0); - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - auto kill_child = Cleanup( - [&] { EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); }); - absl::SleepFor(kWaiterStartupDelay); - - ptr->fetch_add(1); - // This is an ASSERT so that if it fails, we immediately abort the test (and - // kill the subprocess). - ASSERT_THAT(futex_wake(false, ptr, 1), SyscallSucceedsWithValue(1)); - - kill_child.Release(); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST_P(PrivateAndSharedFutexTest, PIBasic) { - std::atomic<int> a = ATOMIC_VAR_INIT(0); - - ASSERT_THAT(futex_lock_pi(IsPrivate(), &a), SyscallSucceeds()); - EXPECT_EQ(a.load(), gettid()); - EXPECT_THAT(futex_lock_pi(IsPrivate(), &a), SyscallFailsWithErrno(EDEADLK)); - - ASSERT_THAT(futex_unlock_pi(IsPrivate(), &a), SyscallSucceeds()); - EXPECT_EQ(a.load(), 0); - EXPECT_THAT(futex_unlock_pi(IsPrivate(), &a), SyscallFailsWithErrno(EPERM)); -} - -TEST_P(PrivateAndSharedFutexTest, PIConcurrency_NoRandomSave) { - DisableSave ds; // Too many syscalls. - - std::atomic<int> a = ATOMIC_VAR_INIT(0); - const bool is_priv = IsPrivate(); - - std::unique_ptr<ScopedThread> threads[100]; - for (size_t i = 0; i < ABSL_ARRAYSIZE(threads); ++i) { - threads[i] = absl::make_unique<ScopedThread>([is_priv, &a] { - for (size_t j = 0; j < 10; ++j) { - ASSERT_THAT(futex_lock_pi(is_priv, &a), SyscallSucceeds()); - EXPECT_EQ(a.load() & FUTEX_TID_MASK, gettid()); - SleepSafe(absl::Milliseconds(5)); - ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds()); - } - }); - } -} - -TEST_P(PrivateAndSharedFutexTest, PIWaiters) { - std::atomic<int> a = ATOMIC_VAR_INIT(0); - const bool is_priv = IsPrivate(); - - ASSERT_THAT(futex_lock_pi(is_priv, &a), SyscallSucceeds()); - EXPECT_EQ(a.load(), gettid()); - - ScopedThread th([is_priv, &a] { - ASSERT_THAT(futex_lock_pi(is_priv, &a), SyscallSucceeds()); - ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds()); - }); - - // Wait until the thread blocks on the futex, setting the waiters bit. - auto start = absl::Now(); - while (a.load() != (FUTEX_WAITERS | gettid())) { - ASSERT_LT(absl::Now() - start, absl::Seconds(5)); - absl::SleepFor(absl::Milliseconds(100)); - } - ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds()); -} - -TEST_P(PrivateAndSharedFutexTest, PITryLock) { - std::atomic<int> a = ATOMIC_VAR_INIT(0); - const bool is_priv = IsPrivate(); - - ASSERT_THAT(futex_trylock_pi(IsPrivate(), &a), SyscallSucceeds()); - EXPECT_EQ(a.load(), gettid()); - - EXPECT_THAT(futex_trylock_pi(is_priv, &a), SyscallFailsWithErrno(EDEADLK)); - ScopedThread th([is_priv, &a] { - EXPECT_THAT(futex_trylock_pi(is_priv, &a), SyscallFailsWithErrno(EAGAIN)); - }); - th.Join(); - - ASSERT_THAT(futex_unlock_pi(IsPrivate(), &a), SyscallSucceeds()); -} - -TEST_P(PrivateAndSharedFutexTest, PITryLockConcurrency_NoRandomSave) { - DisableSave ds; // Too many syscalls. - - std::atomic<int> a = ATOMIC_VAR_INIT(0); - const bool is_priv = IsPrivate(); - - std::unique_ptr<ScopedThread> threads[10]; - for (size_t i = 0; i < ABSL_ARRAYSIZE(threads); ++i) { - threads[i] = absl::make_unique<ScopedThread>([is_priv, &a] { - for (size_t j = 0; j < 10;) { - if (futex_trylock_pi(is_priv, &a) == 0) { - ++j; - EXPECT_EQ(a.load() & FUTEX_TID_MASK, gettid()); - SleepSafe(absl::Milliseconds(5)); - ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds()); - } - } - }); - } -} - -int get_robust_list(int pid, struct robust_list_head** head_ptr, - size_t* len_ptr) { - return syscall(__NR_get_robust_list, pid, head_ptr, len_ptr); -} - -int set_robust_list(struct robust_list_head* head, size_t len) { - return syscall(__NR_set_robust_list, head, len); -} - -TEST(RobustFutexTest, BasicSetGet) { - struct robust_list_head hd = {}; - struct robust_list_head* hd_ptr = &hd; - - // Set! - EXPECT_THAT(set_robust_list(hd_ptr, sizeof(hd)), SyscallSucceedsWithValue(0)); - - // Get! - struct robust_list_head* new_hd_ptr = hd_ptr; - size_t len; - EXPECT_THAT(get_robust_list(0, &new_hd_ptr, &len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(new_hd_ptr, hd_ptr); - EXPECT_EQ(len, sizeof(hd)); -} - -TEST(RobustFutexTest, GetFromOtherTid) { - // Get the current tid and list head. - pid_t tid = gettid(); - struct robust_list_head* hd_ptr = {}; - size_t len; - EXPECT_THAT(get_robust_list(0, &hd_ptr, &len), SyscallSucceedsWithValue(0)); - - // Create a new thread. - ScopedThread t([&] { - // Current tid list head should be different from parent tid. - struct robust_list_head* got_hd_ptr = {}; - EXPECT_THAT(get_robust_list(0, &got_hd_ptr, &len), - SyscallSucceedsWithValue(0)); - EXPECT_NE(hd_ptr, got_hd_ptr); - - // Get the parent list head by passing its tid. - EXPECT_THAT(get_robust_list(tid, &got_hd_ptr, &len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(hd_ptr, got_hd_ptr); - }); - - // Wait for thread. - t.Join(); -} - -TEST(RobustFutexTest, InvalidSize) { - struct robust_list_head* hd = {}; - EXPECT_THAT(set_robust_list(hd, sizeof(*hd) + 1), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(RobustFutexTest, PthreadMutexAttr) { - constexpr int kNumMutexes = 3; - - // Create a bunch of robust mutexes. - pthread_mutexattr_t attrs[kNumMutexes]; - pthread_mutex_t mtxs[kNumMutexes]; - for (int i = 0; i < kNumMutexes; i++) { - TEST_PCHECK(pthread_mutexattr_init(&attrs[i]) == 0); - TEST_PCHECK(pthread_mutexattr_setrobust(&attrs[i], PTHREAD_MUTEX_ROBUST) == - 0); - TEST_PCHECK(pthread_mutex_init(&mtxs[i], &attrs[i]) == 0); - } - - // Start thread to lock the mutexes and then exit. - ScopedThread t([&] { - for (int i = 0; i < kNumMutexes; i++) { - TEST_PCHECK(pthread_mutex_lock(&mtxs[i]) == 0); - } - pthread_exit(NULL); - }); - - // Wait for thread. - t.Join(); - - // Now try to take the mutexes. - for (int i = 0; i < kNumMutexes; i++) { - // Should get EOWNERDEAD. - EXPECT_EQ(pthread_mutex_lock(&mtxs[i]), EOWNERDEAD); - // Make the mutex consistent. - EXPECT_EQ(pthread_mutex_consistent(&mtxs[i]), 0); - // Unlock. - EXPECT_EQ(pthread_mutex_unlock(&mtxs[i]), 0); - } -} - -} // namespace -} // namespace testing -} // namespace gvisor |