Skip to content

Commit

Permalink
Adding policies for all the upgrade and downgrade mutex transitions t…
Browse files Browse the repository at this point in the history
…hat are going to be supported by Synchronized

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
  • Loading branch information
aary authored and Facebook Github Bot 2 committed Aug 5, 2016
1 parent 84f2423 commit 7dd2b58
Show file tree
Hide file tree
Showing 2 changed files with 273 additions and 6 deletions.
125 changes: 119 additions & 6 deletions folly/LockTraits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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
154 changes: 154 additions & 0 deletions folly/test/LockTraitsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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();
}

0 comments on commit 7dd2b58

Please sign in to comment.