Adding policies for all the upgrade and downgrade mutex transitions that are going...
authorAaryaman Sagar <aary@instagram.com>
Fri, 5 Aug 2016 03:05:35 +0000 (20:05 -0700)
committerFacebook Github Bot 2 <facebook-github-bot-2-bot@fb.com>
Fri, 5 Aug 2016 03:08:25 +0000 (20:08 -0700)
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
folly/test/LockTraitsTest.cpp

index 1879e3b..15ffe11 100644 (file)
@@ -462,8 +462,7 @@ unlock_shared_or_unique(Mutex& mutex) {
 /**
  * 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);
@@ -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 <class Mutex>
   static void lock(Mutex& mutex) {
     LockTraits<Mutex>::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 <class Mutex>
   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 <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
index be9f0ec..d77d820 100644 (file)
@@ -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 <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");
@@ -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();
+}