/**
* A lock policy that performs exclusive lock operations.
*/
-class LockPolicyExclusive {
- public:
+struct LockPolicyExclusive {
template <class Mutex>
static void lock(Mutex& mutex) {
LockTraits<Mutex>::lock(mutex);
* A lock policy that performs shared lock operations.
* This policy only works with shared mutex types.
*/
-class LockPolicyShared {
- public:
+struct LockPolicyShared {
template <class Mutex>
static void lock(Mutex& mutex) {
LockTraits<Mutex>::lock_shared(mutex);
* 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 <class Mutex>
static void lock(Mutex& mutex) {
lock_shared_or_unique(mutex);
}
};
+/**
+ * A lock policy with the following mapping
+ *
+ * lock() -> lock_upgrade()
+ * unlock() -> unlock_upgrade()
+ * try_lock_for -> try_lock_upgrade_for()
+ */
+struct LockPolicyUpgrade {
+ template <class Mutex>
+ static void lock(Mutex& mutex) {
+ LockTraits<Mutex>::lock_upgrade(mutex);
+ }
+ template <class Mutex, class Rep, class Period>
+ static bool try_lock_for(
+ Mutex& mutex,
+ const std::chrono::duration<Rep, Period>& timeout) {
+ return LockTraits<Mutex>::try_lock_upgrade_for(mutex, timeout);
+ }
+ template <class Mutex>
+ static void unlock(Mutex& mutex) {
+ LockTraits<Mutex>::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 <class Mutex>
+ static void lock(Mutex& mutex) {
+ LockTraits<Mutex>::unlock_upgrade_and_lock(mutex);
+ }
+ template <class Mutex, class Rep, class Period>
+ static bool try_lock_for(
+ Mutex& mutex,
+ const std::chrono::duration<Rep, Period>& timeout) {
+ return LockTraits<Mutex>::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 <class Mutex>
+ static void lock(Mutex& mutex) {
+ LockTraits<Mutex>::unlock_and_lock_upgrade(mutex);
+ }
+ template <class Mutex, class Rep, class Period>
+ static bool try_lock_for(
+ Mutex& mutex,
+ const std::chrono::duration<Rep, Period>&) {
+ LockTraits<Mutex>::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 <class Mutex>
+ static void lock(Mutex& mutex) {
+ LockTraits<Mutex>::unlock_upgrade_and_lock_shared(mutex);
+ }
+ template <class Mutex, class Rep, class Period>
+ static bool try_lock_for(
+ Mutex& mutex,
+ const std::chrono::duration<Rep, Period>&) {
+ LockTraits<Mutex>::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 <class Mutex>
+ static void lock(Mutex& mutex) {
+ LockTraits<Mutex>::unlock_and_lock_shared(mutex);
+ }
+ template <class Mutex, class Rep, class Period>
+ static bool try_lock_for(
+ Mutex& mutex,
+ const std::chrono::duration<Rep, Period>&) {
+ LockTraits<Mutex>::unlock_and_lock_shared(mutex);
+
+ // downgrade should be non blocking and should succeed
+ return true;
+ }
+};
+
} // 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 <class Rep, class Period>
+ bool try_lock_for(const std::chrono::duration<Rep, Period>&) {
+ EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
+ this->lock_state = CurrentLockState::UNIQUE;
+ return true;
+ }
+
+ template <class Rep, class Period>
+ bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>&) {
+ EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
+ this->lock_state = CurrentLockState::UPGRADE;
+ return true;
+ }
+
+ template <class Rep, class Period>
+ bool try_unlock_upgrade_and_lock_for(
+ const std::chrono::duration<Rep, Period>&) {
+ 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<std::mutex>;
static_assert(!traits::is_timed, "std:mutex is not a timed lock");
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();
+}