From 7dd2b5809516d608c4c8f7da7ee3a8f517440003 Mon Sep 17 00:00:00 2001 From: Aaryaman Sagar Date: Thu, 4 Aug 2016 20:05:35 -0700 Subject: [PATCH] Adding policies for all the upgrade and downgrade mutex transitions that are going to be supported by Synchronized Summary: This diff contains the lock policies that will be used by Synchronized to implement mutex transitions by means of heterogenous RAII mutex wrappers Reviewed By: yfeldblum Differential Revision: D3665020 fbshipit-source-id: a5509dfd58a1dd6cd60a7d3afe929d0da860926d --- folly/LockTraits.h | 125 +++++++++++++++++++++++++-- folly/test/LockTraitsTest.cpp | 154 ++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+), 6 deletions(-) diff --git a/folly/LockTraits.h b/folly/LockTraits.h index 1879e3bc..15ffe112 100644 --- a/folly/LockTraits.h +++ b/folly/LockTraits.h @@ -462,8 +462,7 @@ unlock_shared_or_unique(Mutex& mutex) { /** * A lock policy that performs exclusive lock operations. */ -class LockPolicyExclusive { - public: +struct LockPolicyExclusive { template static void lock(Mutex& mutex) { LockTraits::lock(mutex); @@ -484,8 +483,7 @@ class LockPolicyExclusive { * A lock policy that performs shared lock operations. * This policy only works with shared mutex types. */ -class LockPolicyShared { - public: +struct LockPolicyShared { template static void lock(Mutex& mutex) { LockTraits::lock_shared(mutex); @@ -506,8 +504,7 @@ class LockPolicyShared { * A lock policy that performs a shared lock operation if a shared mutex type * is given, or a normal exclusive lock operation on non-shared mutex types. */ -class LockPolicyShareable { - public: +struct LockPolicyShareable { template static void lock(Mutex& mutex) { lock_shared_or_unique(mutex); @@ -524,4 +521,120 @@ class LockPolicyShareable { } }; +/** + * A lock policy with the following mapping + * + * lock() -> lock_upgrade() + * unlock() -> unlock_upgrade() + * try_lock_for -> try_lock_upgrade_for() + */ +struct LockPolicyUpgrade { + template + static void lock(Mutex& mutex) { + LockTraits::lock_upgrade(mutex); + } + template + static bool try_lock_for( + Mutex& mutex, + const std::chrono::duration& timeout) { + return LockTraits::try_lock_upgrade_for(mutex, timeout); + } + template + static void unlock(Mutex& mutex) { + LockTraits::unlock_upgrade(mutex); + } +}; + +/***************************************************************************** + * Policies for all the transitions from possible mutex levels + ****************************************************************************/ +/** + * A lock policy with the following mapping + * + * lock() -> unlock_upgrade_and_lock() + * unlock() -> unlock() + * try_lock_for -> try_unlock_upgrade_and_lock_for() + */ +struct LockPolicyFromUpgradeToExclusive : public LockPolicyExclusive { + template + static void lock(Mutex& mutex) { + LockTraits::unlock_upgrade_and_lock(mutex); + } + template + static bool try_lock_for( + Mutex& mutex, + const std::chrono::duration& timeout) { + return LockTraits::try_unlock_upgrade_and_lock_for(mutex, timeout); + } +}; + +/** + * A lock policy with the following mapping + * + * lock() -> unlock_and_lock_upgrade() + * unlock() -> unlock_upgrade() + * try_lock_for -> unlock_and_lock_upgrade() + */ +struct LockPolicyFromExclusiveToUpgrade : public LockPolicyUpgrade { + template + static void lock(Mutex& mutex) { + LockTraits::unlock_and_lock_upgrade(mutex); + } + template + static bool try_lock_for( + Mutex& mutex, + const std::chrono::duration&) { + LockTraits::unlock_and_lock_upgrade(mutex); + + // downgrade should be non blocking and should succeed + return true; + } +}; + +/** + * A lock policy with the following mapping + * + * lock() -> unlock_upgrade_and_lock_shared() + * unlock() -> unlock_shared() + * try_lock_for -> unlock_upgrade_and_lock_shared() + */ +struct LockPolicyFromUpgradeToShared : public LockPolicyShared { + template + static void lock(Mutex& mutex) { + LockTraits::unlock_upgrade_and_lock_shared(mutex); + } + template + static bool try_lock_for( + Mutex& mutex, + const std::chrono::duration&) { + LockTraits::unlock_upgrade_and_lock_shared(mutex); + + // downgrade should be non blocking and should succeed + return true; + } +}; + +/** + * A lock policy with the following mapping + * + * lock() -> unlock_and_lock_shared() + * unlock() -> unlock_shared() + * try_lock_for() -> unlock_and_lock_shared() + */ +struct LockPolicyFromExclusiveToShared : public LockPolicyShared { + template + static void lock(Mutex& mutex) { + LockTraits::unlock_and_lock_shared(mutex); + } + template + static bool try_lock_for( + Mutex& mutex, + const std::chrono::duration&) { + LockTraits::unlock_and_lock_shared(mutex); + + // downgrade should be non blocking and should succeed + return true; + } +}; + } // folly diff --git a/folly/test/LockTraitsTest.cpp b/folly/test/LockTraitsTest.cpp index be9f0ec9..d77d8206 100644 --- a/folly/test/LockTraitsTest.cpp +++ b/folly/test/LockTraitsTest.cpp @@ -27,6 +27,83 @@ using namespace folly; static constexpr auto one_ms = std::chrono::milliseconds(1); +/** + * Test mutex to help to automate assertions + */ +class FakeAllPowerfulAssertingMutex { + public: + enum class CurrentLockState { UNLOCKED, SHARED, UPGRADE, UNIQUE }; + + void lock() { + EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); + this->lock_state = CurrentLockState::UNIQUE; + } + void unlock() { + EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE); + this->lock_state = CurrentLockState::UNLOCKED; + } + void lock_shared() { + EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); + this->lock_state = CurrentLockState::SHARED; + } + void unlock_shared() { + EXPECT_EQ(this->lock_state, CurrentLockState::SHARED); + this->lock_state = CurrentLockState::UNLOCKED; + } + void lock_upgrade() { + EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); + this->lock_state = CurrentLockState::UPGRADE; + } + void unlock_upgrade() { + EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); + this->lock_state = CurrentLockState::UNLOCKED; + } + + void unlock_upgrade_and_lock() { + EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); + this->lock_state = CurrentLockState::UNIQUE; + } + void unlock_and_lock_upgrade() { + EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE); + this->lock_state = CurrentLockState::UPGRADE; + } + void unlock_and_lock_shared() { + EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE); + this->lock_state = CurrentLockState::SHARED; + } + void unlock_upgrade_and_lock_shared() { + EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); + this->lock_state = CurrentLockState::SHARED; + } + + template + bool try_lock_for(const std::chrono::duration&) { + EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); + this->lock_state = CurrentLockState::UNIQUE; + return true; + } + + template + bool try_lock_upgrade_for(const std::chrono::duration&) { + EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); + this->lock_state = CurrentLockState::UPGRADE; + return true; + } + + template + bool try_unlock_upgrade_and_lock_for( + const std::chrono::duration&) { + EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); + this->lock_state = CurrentLockState::UNIQUE; + return true; + } + + /* + * Initialize the FakeMutex with an unlocked state + */ + CurrentLockState lock_state{CurrentLockState::UNLOCKED}; +}; + TEST(LockTraits, std_mutex) { using traits = LockTraits; static_assert(!traits::is_timed, "std:mutex is not a timed lock"); @@ -266,3 +343,80 @@ TEST(LockTraits, boost_shared_mutex) { unlock_shared_or_unique(mutex); } #endif // FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES + +/** + * Chain the asserts from the previous test to the next lock, unlock or + * upgrade method calls. Each making sure that the previous was correct. + */ +TEST(LockTraits, LockPolicy) { + using Mutex = FakeAllPowerfulAssertingMutex; + Mutex mutex; + + // test the lock and unlock functions + LockPolicyUpgrade::lock(mutex); + mutex.unlock_upgrade(); + mutex.lock_upgrade(); + LockPolicyUpgrade::unlock(mutex); + + mutex.lock_upgrade(); + LockPolicyFromUpgradeToExclusive::lock(mutex); + mutex.unlock(); + mutex.lock(); + LockPolicyFromUpgradeToExclusive::unlock(mutex); + + mutex.lock(); + LockPolicyFromExclusiveToUpgrade::lock(mutex); + mutex.unlock_upgrade(); + mutex.lock_upgrade(); + LockPolicyFromExclusiveToUpgrade::unlock(mutex); + + mutex.lock_upgrade(); + LockPolicyFromUpgradeToShared::lock(mutex); + mutex.unlock_shared(); + mutex.lock_shared(); + LockPolicyFromUpgradeToShared::unlock(mutex); + + mutex.lock(); + LockPolicyFromExclusiveToShared::lock(mutex); + mutex.unlock_shared(); + mutex.lock_shared(); + LockPolicyFromExclusiveToShared::unlock(mutex); + + EXPECT_EQ(mutex.lock_state, Mutex::CurrentLockState::UNLOCKED); +} + +/** + * Similar to the test above but tests the timed version of the updates + */ +TEST(LockTraits, LockPolicyTimed) { + using Mutex = FakeAllPowerfulAssertingMutex; + Mutex mutex; + + bool gotLock = LockPolicyUpgrade::try_lock_for(mutex, one_ms); + EXPECT_TRUE(gotLock) << "Should have been able to acquire the fake mutex"; + LockPolicyUpgrade::unlock(mutex); + + mutex.lock_upgrade(); + gotLock = LockPolicyFromUpgradeToExclusive::try_lock_for(mutex, one_ms); + EXPECT_TRUE(gotLock) + << "Should have been able to upgrade from upgrade to unique"; + mutex.unlock(); + + mutex.lock(); + gotLock = LockPolicyFromExclusiveToUpgrade::try_lock_for(mutex, one_ms); + EXPECT_TRUE(gotLock) << "Should have been able to downgrade from exclusive " + "to upgrade"; + mutex.unlock_upgrade(); + + mutex.lock_upgrade(); + gotLock = LockPolicyFromUpgradeToShared::try_lock_for(mutex, one_ms); + EXPECT_TRUE(gotLock) << "Should have been able to downgrade from upgrade to " + "shared"; + mutex.unlock_shared(); + + mutex.lock(); + gotLock = LockPolicyFromExclusiveToShared::try_lock_for(mutex, one_ms); + EXPECT_TRUE(gotLock) << "Should have been able to downgrade from exclusive " + "to shared"; + mutex.unlock_shared(); +} -- 2.34.1