summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/futex.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/futex.cc')
-rw-r--r--test/syscalls/linux/futex.cc834
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