From 0b76887147820a809beaa497ede8dc4f7b7b120a Mon Sep 17 00:00:00 2001 From: Fabricio Voznika Date: Tue, 5 Mar 2019 23:39:14 -0800 Subject: Priority-inheritance futex implementation It is Implemented without the priority inheritance part given that gVisor defers scheduling decisions to Go runtime and doesn't have control over it. PiperOrigin-RevId: 236989545 Change-Id: I714c8ca0798743ecf3167b14ffeb5cd834302560 --- test/syscalls/linux/BUILD | 1 + test/syscalls/linux/futex.cc | 115 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 4c818238b..2c214925e 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -808,6 +808,7 @@ cc_binary( "//test/util:cleanup", "//test/util:file_descriptor", "//test/util:memory_util", + "//test/util:save_util", "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", diff --git a/test/syscalls/linux/futex.cc b/test/syscalls/linux/futex.cc index 6fa284013..35933b660 100644 --- a/test/syscalls/linux/futex.cc +++ b/test/syscalls/linux/futex.cc @@ -32,6 +32,7 @@ #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" @@ -118,6 +119,30 @@ int futex_wake_op(bool priv, std::atomic* uaddr1, std::atomic* uaddr2, return syscall(SYS_futex, uaddr1, op, nwake1, nwake2, uaddr2, sub_op); } +int futex_lock_pi(bool priv, std::atomic* uaddr) { + int op = FUTEX_LOCK_PI; + if (priv) { + op |= FUTEX_PRIVATE_FLAG; + } + return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr); +} + +int futex_trylock_pi(bool priv, std::atomic* uaddr) { + int op = FUTEX_TRYLOCK_PI; + if (priv) { + op |= FUTEX_PRIVATE_FLAG; + } + return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr); +} + +int futex_unlock_pi(bool priv, std::atomic* uaddr) { + int op = FUTEX_UNLOCK_PI; + if (priv) { + op |= FUTEX_PRIVATE_FLAG; + } + 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 { @@ -589,7 +614,95 @@ TEST(SharedFutexTest, WakeInterprocessFile_NoRandomSave) { << " status " << status; } -} // namespace +TEST_P(PrivateAndSharedFutexTest, PIBasic) { + std::atomic 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 a = ATOMIC_VAR_INIT(0); + const bool is_priv = IsPrivate(); + + std::unique_ptr threads[100]; + for (size_t i = 0; i < ABSL_ARRAYSIZE(threads); ++i) { + threads[i] = absl::make_unique([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 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 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 a = ATOMIC_VAR_INIT(0); + const bool is_priv = IsPrivate(); + + std::unique_ptr threads[100]; + for (size_t i = 0; i < ABSL_ARRAYSIZE(threads); ++i) { + threads[i] = absl::make_unique([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()); + } + } + }); + } +} + +} // namespace } // namespace testing } // namespace gvisor -- cgit v1.2.3