/*
- * Copyright 2016 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
// Test bed for folly/Synchronized.h
+#include <folly/LockTraitsBoost.h>
#include <folly/Portability.h>
#include <folly/RWSpinLock.h>
#include <folly/SharedMutex.h>
#include <folly/SpinLock.h>
#include <folly/Synchronized.h>
+#include <folly/portability/GTest.h>
#include <folly/test/SynchronizedTestLib.h>
-#include <gtest/gtest.h>
-namespace {
+using namespace folly::sync_tests;
template <class Mutex>
class SynchronizedTest : public testing::Test {};
-using SynchronizedTestTypes = testing::Types
- < folly::SharedMutexReadPriority
- , folly::SharedMutexWritePriority
- , std::mutex
- , std::recursive_mutex
-#if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
- , std::timed_mutex
- , std::recursive_timed_mutex
+using SynchronizedTestTypes = testing::Types<
+ folly::SharedMutexReadPriority,
+ folly::SharedMutexWritePriority,
+ std::mutex,
+ std::recursive_mutex,
+#if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
+ std::timed_mutex,
+ std::recursive_timed_mutex,
#endif
- , boost::mutex
- , boost::recursive_mutex
-#if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
- , boost::timed_mutex
- , boost::recursive_timed_mutex
+ boost::mutex,
+ boost::recursive_mutex,
+#if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
+ boost::timed_mutex,
+ boost::recursive_timed_mutex,
#endif
- , boost::shared_mutex
- , folly::SpinLock
#ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
- , folly::RWTicketSpinLock32
- , folly::RWTicketSpinLock64
+ folly::RWTicketSpinLock32,
+ folly::RWTicketSpinLock64,
#endif
- >;
+ boost::shared_mutex,
+ folly::SpinLock>;
TYPED_TEST_CASE(SynchronizedTest, SynchronizedTestTypes);
TYPED_TEST(SynchronizedTest, Basic) {
testBasic<TypeParam>();
}
+TYPED_TEST(SynchronizedTest, WithLock) {
+ testWithLock<TypeParam>();
+}
+
+TYPED_TEST(SynchronizedTest, Unlock) {
+ testUnlock<TypeParam>();
+}
+
+TYPED_TEST(SynchronizedTest, Deprecated) {
+ testDeprecated<TypeParam>();
+}
+
TYPED_TEST(SynchronizedTest, Concurrency) {
testConcurrency<TypeParam>();
}
+TYPED_TEST(SynchronizedTest, AcquireLocked) {
+ testAcquireLocked<TypeParam>();
+}
+
+TYPED_TEST(SynchronizedTest, AcquireLockedWithConst) {
+ testAcquireLockedWithConst<TypeParam>();
+}
+
TYPED_TEST(SynchronizedTest, DualLocking) {
testDualLocking<TypeParam>();
}
template <class Mutex>
class SynchronizedTimedTest : public testing::Test {};
-using SynchronizedTimedTestTypes = testing::Types
- < folly::SharedMutexReadPriority
- , folly::SharedMutexWritePriority
-#if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
- , std::timed_mutex
- , std::recursive_timed_mutex
- , boost::timed_mutex
- , boost::recursive_timed_mutex
- , boost::shared_mutex
+using SynchronizedTimedTestTypes = testing::Types<
+#if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
+ std::timed_mutex,
+ std::recursive_timed_mutex,
+ boost::timed_mutex,
+ boost::recursive_timed_mutex,
+ boost::shared_mutex,
#endif
#ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
- , folly::RWTicketSpinLock32
- , folly::RWTicketSpinLock64
+ folly::RWTicketSpinLock32,
+ folly::RWTicketSpinLock64,
#endif
- >;
+ folly::SharedMutexReadPriority,
+ folly::SharedMutexWritePriority>;
TYPED_TEST_CASE(SynchronizedTimedTest, SynchronizedTimedTestTypes);
+TYPED_TEST(SynchronizedTimedTest, Timed) {
+ testTimed<TypeParam>();
+}
+
TYPED_TEST(SynchronizedTimedTest, TimedSynchronized) {
testTimedSynchronized<TypeParam>();
}
template <class Mutex>
class SynchronizedTimedWithConstTest : public testing::Test {};
-using SynchronizedTimedWithConstTestTypes = testing::Types
- < folly::SharedMutexReadPriority
- , folly::SharedMutexWritePriority
-#if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
- , boost::shared_mutex
+using SynchronizedTimedWithConstTestTypes = testing::Types<
+#if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
+ boost::shared_mutex,
#endif
#ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
- , folly::RWTicketSpinLock32
- , folly::RWTicketSpinLock64
+ folly::RWTicketSpinLock32,
+ folly::RWTicketSpinLock64,
#endif
- >;
+ folly::SharedMutexReadPriority,
+ folly::SharedMutexWritePriority>;
TYPED_TEST_CASE(
SynchronizedTimedWithConstTest, SynchronizedTimedWithConstTestTypes);
+TYPED_TEST(SynchronizedTimedWithConstTest, TimedShared) {
+ testTimedShared<TypeParam>();
+}
+
TYPED_TEST(SynchronizedTimedWithConstTest, TimedSynchronizeWithConst) {
testTimedSynchronizedWithConst<TypeParam>();
}
// This class is specialized only to be uesed in SynchronizedLockTest
class FakeMutex {
public:
- bool lock() {
+ void lock() {
++lockCount_;
- return true;
}
- bool unlock() {
+ void unlock() {
++unlockCount_;
- return true;
}
static CountPair getLockUnlockCount() {
// process
static FOLLY_TLS int lockCount_;
static FOLLY_TLS int unlockCount_;
-
- // Adapters for Synchronized<>
- friend void acquireReadWrite(FakeMutex& lock) { lock.lock(); }
- friend void releaseReadWrite(FakeMutex& lock) { lock.unlock(); }
};
FOLLY_TLS int FakeMutex::lockCount_{0};
FOLLY_TLS int FakeMutex::unlockCount_{0};
}
};
-// 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
-TEST_F(SynchronizedLockTest, SyncUnSync) {
- folly::Synchronized<std::vector<int>, FakeMutex> obj;
- EXPECT_EQ((CountPair{0, 0}), FakeMutex::getLockUnlockCount());
- SYNCHRONIZED(obj) {
- EXPECT_EQ((CountPair{1, 0}), FakeMutex::getLockUnlockCount());
- UNSYNCHRONIZED(obj) {
- EXPECT_EQ((CountPair{1, 1}), FakeMutex::getLockUnlockCount());
- }
- EXPECT_EQ((CountPair{2, 1}), FakeMutex::getLockUnlockCount());
- }
- EXPECT_EQ((CountPair{2, 2}), FakeMutex::getLockUnlockCount());
-}
-
-// Nested SYNCHRONIZED UNSYNCHRONIZED test, 2 levels for each are used here
-TEST_F(SynchronizedLockTest, NestedSyncUnSync) {
- folly::Synchronized<std::vector<int>, FakeMutex> obj;
- EXPECT_EQ((CountPair{0, 0}), FakeMutex::getLockUnlockCount());
- SYNCHRONIZED(objCopy, obj) {
- EXPECT_EQ((CountPair{1, 0}), FakeMutex::getLockUnlockCount());
- SYNCHRONIZED(obj) {
- EXPECT_EQ((CountPair{2, 0}), FakeMutex::getLockUnlockCount());
- UNSYNCHRONIZED(obj) {
- EXPECT_EQ((CountPair{2, 1}), FakeMutex::getLockUnlockCount());
- UNSYNCHRONIZED(obj) {
- EXPECT_EQ((CountPair{2, 2}),
- FakeMutex::getLockUnlockCount());
- }
- EXPECT_EQ((CountPair{3, 2}), FakeMutex::getLockUnlockCount());
- }
- EXPECT_EQ((CountPair{4, 2}), FakeMutex::getLockUnlockCount());
- }
- EXPECT_EQ((CountPair{4, 3}), FakeMutex::getLockUnlockCount());
- }
- EXPECT_EQ((CountPair{4, 4}), FakeMutex::getLockUnlockCount());
-}
-
-// Different nesting behavior, UNSYNCHRONIZED called on differen depth of
-// SYNCHRONIZED
-TEST_F(SynchronizedLockTest, NestedSyncUnSync2) {
- folly::Synchronized<std::vector<int>, FakeMutex> obj;
- EXPECT_EQ((CountPair{0, 0}), FakeMutex::getLockUnlockCount());
- SYNCHRONIZED(objCopy, obj) {
- EXPECT_EQ((CountPair{1, 0}), FakeMutex::getLockUnlockCount());
- SYNCHRONIZED(obj) {
- EXPECT_EQ((CountPair{2, 0}), FakeMutex::getLockUnlockCount());
- UNSYNCHRONIZED(obj) {
- EXPECT_EQ((CountPair{2, 1}), FakeMutex::getLockUnlockCount());
- }
- EXPECT_EQ((CountPair{3, 1}), FakeMutex::getLockUnlockCount());
- }
- EXPECT_EQ((CountPair{3, 2}), FakeMutex::getLockUnlockCount());
- UNSYNCHRONIZED(obj) {
- EXPECT_EQ((CountPair{3, 3}), FakeMutex::getLockUnlockCount());
- }
- EXPECT_EQ((CountPair{4, 3}), FakeMutex::getLockUnlockCount());
- }
- EXPECT_EQ((CountPair{4, 4}), FakeMutex::getLockUnlockCount());
+/**
+ * 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{};
+ }
+};
+
+TEST_F(SynchronizedLockTest, TestCopyConstructibleValues) {
+ struct NonCopyConstructible {
+ NonCopyConstructible(const NonCopyConstructible&) = delete;
+ NonCopyConstructible& operator=(const NonCopyConstructible&) = delete;
+ };
+ struct CopyConstructible {};
+ EXPECT_FALSE(std::is_copy_constructible<
+ folly::Synchronized<NonCopyConstructible>>::value);
+ EXPECT_FALSE(std::is_copy_assignable<
+ folly::Synchronized<NonCopyConstructible>>::value);
+ EXPECT_TRUE(std::is_copy_constructible<
+ folly::Synchronized<CopyConstructible>>::value);
+ EXPECT_TRUE(
+ std::is_copy_assignable<folly::Synchronized<CopyConstructible>>::value);
+}
+
+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.moveFromUpgradeToRead();
+ 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.moveFromWriteToRead();
+ 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.moveFromUpgradeToRead();
+ 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.moveFromWriteToRead();
+ 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);
}