}
};
+/**
+ * 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
}
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);
+}