Adding upgradable locks to Synchronized
[folly.git] / folly / test / SynchronizedTest.cpp
index 5826ef28bfce8c34ab1cfda853d0913d33aa1833..b734964533a72a1ed1464e349d7b78af774eafa7 100644 (file)
@@ -189,6 +189,152 @@ class SynchronizedLockTest : public testing::Test {
   }
 };
 
+/**
+ * To avoid typing
+ */
+// static constexpr auto one_ms = std::chrono::milliseconds(1);
+
+/**
+ * Test mutex to help to automate assertions, taken from LockTraitsTest.cpp
+ */
+class FakeAllPowerfulAssertingMutexInternal {
+ 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};
+};
+
+/**
+ * The following works around the internal mutex for synchronized being
+ * private
+ *
+ * This is horridly thread unsafe.
+ */
+static FakeAllPowerfulAssertingMutexInternal globalAllPowerfulAssertingMutex;
+
+class FakeAllPowerfulAssertingMutex {
+ public:
+  void lock() {
+    globalAllPowerfulAssertingMutex.lock();
+  }
+  void unlock() {
+    globalAllPowerfulAssertingMutex.unlock();
+  }
+  void lock_shared() {
+    globalAllPowerfulAssertingMutex.lock_shared();
+  }
+  void unlock_shared() {
+    globalAllPowerfulAssertingMutex.unlock_shared();
+  }
+  void lock_upgrade() {
+    globalAllPowerfulAssertingMutex.lock_upgrade();
+  }
+  void unlock_upgrade() {
+    globalAllPowerfulAssertingMutex.unlock_upgrade();
+  }
+
+  void unlock_upgrade_and_lock() {
+    globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock();
+  }
+  void unlock_and_lock_upgrade() {
+    globalAllPowerfulAssertingMutex.unlock_and_lock_upgrade();
+  }
+  void unlock_and_lock_shared() {
+    globalAllPowerfulAssertingMutex.unlock_and_lock_shared();
+  }
+  void unlock_upgrade_and_lock_shared() {
+    globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock_shared();
+  }
+
+  template <class Rep, class Period>
+  bool try_lock_for(const std::chrono::duration<Rep, Period>& arg) {
+    return globalAllPowerfulAssertingMutex.try_lock_for(arg);
+  }
+
+  template <class Rep, class Period>
+  bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>& arg) {
+    return globalAllPowerfulAssertingMutex.try_lock_upgrade_for(arg);
+  }
+
+  template <class Rep, class Period>
+  bool try_unlock_upgrade_and_lock_for(
+      const std::chrono::duration<Rep, Period>& arg) {
+    return globalAllPowerfulAssertingMutex.try_unlock_upgrade_and_lock_for(arg);
+  }
+
+  // reset state on destruction
+  ~FakeAllPowerfulAssertingMutex() {
+    globalAllPowerfulAssertingMutex = FakeAllPowerfulAssertingMutexInternal{};
+  }
+};
+
 // Single level of SYNCHRONIZED and UNSYNCHRONIZED, although nested test are
 // super set of it, it is possible single level test passes while nested tests
 // fail
@@ -253,3 +399,169 @@ TEST_F(SynchronizedLockTest, NestedSyncUnSync2) {
   }
   EXPECT_EQ((CountPair{4, 4}), FakeMutex::getLockUnlockCount());
 }
+
+TEST_F(SynchronizedLockTest, UpgradableLocking) {
+  folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync;
+
+  // sanity assert
+  static_assert(
+      std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value,
+      "The ulock function was not well configured, blame aary@instagram.com");
+
+  {
+    auto ulock = sync.ulock();
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
+  }
+
+  // should be unlocked here
+  EXPECT_EQ(
+      globalAllPowerfulAssertingMutex.lock_state,
+      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
+
+  // test going from upgrade to exclusive
+  {
+    auto ulock = sync.ulock();
+    auto wlock = ulock.moveFromUpgradeToWrite();
+    EXPECT_EQ(static_cast<bool>(ulock), false);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
+  }
+
+  // should be unlocked here
+  EXPECT_EQ(
+      globalAllPowerfulAssertingMutex.lock_state,
+      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
+
+  // test going from upgrade to shared
+  {
+    auto ulock = sync.ulock();
+    auto slock = ulock.moveFromUpgradeToShared();
+    EXPECT_EQ(static_cast<bool>(ulock), false);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
+  }
+
+  // should be unlocked here
+  EXPECT_EQ(
+      globalAllPowerfulAssertingMutex.lock_state,
+      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
+
+  // test going from exclusive to upgrade
+  {
+    auto wlock = sync.wlock();
+    auto ulock = wlock.moveFromWriteToUpgrade();
+    EXPECT_EQ(static_cast<bool>(wlock), false);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
+  }
+
+  // should be unlocked here
+  EXPECT_EQ(
+      globalAllPowerfulAssertingMutex.lock_state,
+      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
+
+  // test going from exclusive to shared
+  {
+    auto wlock = sync.wlock();
+    auto slock = wlock.moveFromWriteToShared();
+    EXPECT_EQ(static_cast<bool>(wlock), false);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
+  }
+
+  // should be unlocked here
+  EXPECT_EQ(
+      globalAllPowerfulAssertingMutex.lock_state,
+      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
+}
+
+TEST_F(SynchronizedLockTest, UpgradableLockingWithULock) {
+  folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync;
+
+  // sanity assert
+  static_assert(
+      std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value,
+      "The ulock function was not well configured, blame aary@instagram.com");
+
+  // test from upgrade to write
+  sync.withULockPtr([](auto ulock) {
+    EXPECT_EQ(static_cast<bool>(ulock), true);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
+
+    auto wlock = ulock.moveFromUpgradeToWrite();
+    EXPECT_EQ(static_cast<bool>(ulock), false);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
+  });
+
+  // should be unlocked here
+  EXPECT_EQ(
+      globalAllPowerfulAssertingMutex.lock_state,
+      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
+
+  // test from write to upgrade
+  sync.withWLockPtr([](auto wlock) {
+    EXPECT_EQ(static_cast<bool>(wlock), true);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
+
+    auto ulock = wlock.moveFromWriteToUpgrade();
+    EXPECT_EQ(static_cast<bool>(wlock), false);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
+  });
+
+  // should be unlocked here
+  EXPECT_EQ(
+      globalAllPowerfulAssertingMutex.lock_state,
+      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
+
+  // test from upgrade to shared
+  sync.withULockPtr([](auto ulock) {
+    EXPECT_EQ(static_cast<bool>(ulock), true);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
+
+    auto slock = ulock.moveFromUpgradeToShared();
+    EXPECT_EQ(static_cast<bool>(ulock), false);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
+  });
+
+  // should be unlocked here
+  EXPECT_EQ(
+      globalAllPowerfulAssertingMutex.lock_state,
+      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
+
+  // test from write to shared
+  sync.withWLockPtr([](auto wlock) {
+    EXPECT_EQ(static_cast<bool>(wlock), true);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
+
+    auto slock = wlock.moveFromWriteToShared();
+    EXPECT_EQ(static_cast<bool>(wlock), false);
+    EXPECT_EQ(
+        globalAllPowerfulAssertingMutex.lock_state,
+        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
+  });
+
+  // should be unlocked here
+  EXPECT_EQ(
+      globalAllPowerfulAssertingMutex.lock_state,
+      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
+}