Commit 35ab212c authored by Aaryaman Sagar's avatar Aaryaman Sagar Committed by Facebook Github Bot

Add tryWLock, tryRLock, tryULock, tryWLock to folly::Synchronized

Summary:
Add `try_lock` methods to `folly::Synchronized` and add the corresponding
extensions to `LockTraits`.  These return `LockedPtr`s that are valid and
convert to true when the lock operation was successful, null otherwise

override-unit-failure

Reviewed By: yfeldblum, ot

Differential Revision: D7651765

fbshipit-source-id: eb85987a2e4476cd916747dfc2ae69d124690ee6
parent 9e63a755
......@@ -130,6 +130,13 @@ struct LockTraitsImpl<Mutex, MutexLevel::UNIQUE, false> {
static void unlock(Mutex& mutex) {
mutex.unlock();
}
/**
* Try to acquire the mutex
*/
static bool try_lock(Mutex& mutex) {
return mutex.try_lock();
}
};
/**
......@@ -156,6 +163,13 @@ struct LockTraitsImpl<Mutex, MutexLevel::SHARED, false>
static void unlock_shared(Mutex& mutex) {
mutex.unlock_shared();
}
/**
* Try to acquire the mutex in shared mode
*/
static bool try_lock_shared(Mutex& mutex) {
return mutex.try_lock_shared();
}
};
/**
......@@ -163,6 +177,7 @@ struct LockTraitsImpl<Mutex, MutexLevel::SHARED, false>
*
* m.lock_upgrade()
* m.unlock_upgrade()
* m.try_lock_upgrade()
*
* m.unlock_upgrade_and_lock()
*
......@@ -206,6 +221,13 @@ struct LockTraitsImpl<Mutex, MutexLevel::UPGRADE, false>
mutex.unlock_upgrade();
}
/**
* Try and acquire the lock in upgrade mode
*/
static bool try_lock_upgrade(Mutex& mutex) {
return mutex.try_lock_upgrade();
}
/**
* Upgrade from an upgradable state to an exclusive state
*/
......@@ -350,15 +372,18 @@ struct LockTraitsImpl<Mutex, MutexLevel::UPGRADE, true>
* The following static methods always exist:
* - lock(Mutex& mutex)
* - unlock(Mutex& mutex)
* - try_lock(Mutex& mutex)
*
* The following static methods may exist, depending on is_shared, is_timed
* and is_upgrade:
* - lock_shared()
* - try_lock_shared()
*
* - try_lock_for()
* - try_lock_shared_for()
*
* - lock_upgrade()
* - try_lock_upgrade()
* - unlock_upgrade_and_lock()
* - unlock_and_lock_upgrade()
* - unlock_and_lock_shared()
......@@ -452,8 +477,9 @@ unlock_shared_or_unique(Mutex& mutex) {
*/
struct LockPolicyExclusive {
template <class Mutex>
static void lock(Mutex& mutex) {
static std::true_type lock(Mutex& mutex) {
LockTraits<Mutex>::lock(mutex);
return std::true_type{};
}
template <class Mutex, class Rep, class Period>
static bool try_lock_for(
......@@ -473,8 +499,9 @@ struct LockPolicyExclusive {
*/
struct LockPolicyShared {
template <class Mutex>
static void lock(Mutex& mutex) {
static std::true_type lock(Mutex& mutex) {
LockTraits<Mutex>::lock_shared(mutex);
return std::true_type{};
}
template <class Mutex, class Rep, class Period>
static bool try_lock_for(
......@@ -494,8 +521,9 @@ struct LockPolicyShared {
*/
struct LockPolicyShareable {
template <class Mutex>
static void lock(Mutex& mutex) {
static std::true_type lock(Mutex& mutex) {
lock_shared_or_unique(mutex);
return std::true_type{};
}
template <class Mutex, class Rep, class Period>
static bool try_lock_for(
......@@ -518,8 +546,9 @@ struct LockPolicyShareable {
*/
struct LockPolicyUpgrade {
template <class Mutex>
static void lock(Mutex& mutex) {
static std::true_type lock(Mutex& mutex) {
LockTraits<Mutex>::lock_upgrade(mutex);
return std::true_type{};
}
template <class Mutex, class Rep, class Period>
static bool try_lock_for(
......@@ -533,6 +562,54 @@ struct LockPolicyUpgrade {
}
};
/*****************************************************************************
* Policies for optional mutex locking
****************************************************************************/
/**
* A lock policy that tries to acquire write locks and returns true or false
* based on whether the lock operation succeeds
*/
struct LockPolicyTryExclusive {
template <class Mutex>
static bool lock(Mutex& mutex) {
return LockTraits<Mutex>::try_lock(mutex);
}
template <class Mutex>
static void unlock(Mutex& mutex) {
LockTraits<Mutex>::unlock(mutex);
}
};
/**
* A lock policy that tries to acquire a read lock and returns true or false
* based on whether the lock operation succeeds
*/
struct LockPolicyTryShared {
template <class Mutex>
static bool lock(Mutex& mutex) {
return LockTraits<Mutex>::try_lock_shared(mutex);
}
template <class Mutex>
static void unlock(Mutex& mutex) {
LockTraits<Mutex>::unlock_shared(mutex);
}
};
/**
* A lock policy that tries to acquire an upgrade lock and returns true or
* false based on whether the lock operation succeeds
*/
struct LockPolicyTryUpgrade {
template <class Mutex>
static bool lock(Mutex& mutex) {
return LockTraits<Mutex>::try_lock_upgrade(mutex);
}
template <class Mutex>
static void unlock(Mutex& mutex) {
LockTraits<Mutex>::unlock_upgrade(mutex);
}
};
/*****************************************************************************
* Policies for all the transitions from possible mutex levels
****************************************************************************/
......@@ -545,8 +622,9 @@ struct LockPolicyUpgrade {
*/
struct LockPolicyFromUpgradeToExclusive : public LockPolicyExclusive {
template <class Mutex>
static void lock(Mutex& mutex) {
static std::true_type lock(Mutex& mutex) {
LockTraits<Mutex>::unlock_upgrade_and_lock(mutex);
return std::true_type{};
}
template <class Mutex, class Rep, class Period>
static bool try_lock_for(
......@@ -565,8 +643,9 @@ struct LockPolicyFromUpgradeToExclusive : public LockPolicyExclusive {
*/
struct LockPolicyFromExclusiveToUpgrade : public LockPolicyUpgrade {
template <class Mutex>
static void lock(Mutex& mutex) {
static std::true_type lock(Mutex& mutex) {
LockTraits<Mutex>::unlock_and_lock_upgrade(mutex);
return std::true_type{};
}
template <class Mutex, class Rep, class Period>
static bool try_lock_for(
......@@ -588,8 +667,9 @@ struct LockPolicyFromExclusiveToUpgrade : public LockPolicyUpgrade {
*/
struct LockPolicyFromUpgradeToShared : public LockPolicyShared {
template <class Mutex>
static void lock(Mutex& mutex) {
static std::true_type lock(Mutex& mutex) {
LockTraits<Mutex>::unlock_upgrade_and_lock_shared(mutex);
return std::true_type{};
}
template <class Mutex, class Rep, class Period>
static bool try_lock_for(
......@@ -611,8 +691,9 @@ struct LockPolicyFromUpgradeToShared : public LockPolicyShared {
*/
struct LockPolicyFromExclusiveToShared : public LockPolicyShared {
template <class Mutex>
static void lock(Mutex& mutex) {
static std::true_type lock(Mutex& mutex) {
LockTraits<Mutex>::unlock_and_lock_shared(mutex);
return std::true_type{};
}
template <class Mutex, class Rep, class Period>
static bool try_lock_for(
......
......@@ -31,6 +31,9 @@
#include <folly/SharedMutex.h>
#include <folly/Traits.h>
#include <folly/Utility.h>
#include <folly/container/Foreach.h>
#include <folly/functional/Invoke.h>
#include <folly/portability/GTest.h>
#include <glog/logging.h>
#include <mutex>
#include <type_traits>
......@@ -85,6 +88,11 @@ class SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
using ConstLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyShared>;
using TryWLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyTryExclusive>;
using ConstTryWLockedPtr =
::folly::LockedPtr<const Subclass, LockPolicyTryExclusive>;
using TryRLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyTryShared>;
/**
* Acquire an exclusive lock, and return a LockedPtr that can be used to
* safely access the datum.
......@@ -99,6 +107,20 @@ class SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
return ConstWLockedPtr(static_cast<const Subclass*>(this));
}
/**
* Attempts to acquire the lock in exclusive mode. If acquisition is
* unsuccessful, the returned LockedPtr will be null.
*
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
* validity.)
*/
TryWLockedPtr tryWLock() {
return TryWLockedPtr{static_cast<Subclass*>(this)};
}
ConstTryWLockedPtr tryWLock() const {
return ConstTryWLockedPtr{static_cast<const Subclass*>(this)};
}
/**
* Acquire a read lock, and return a ConstLockedPtr that can be used to
* safely access the datum.
......@@ -107,11 +129,23 @@ class SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
return ConstLockedPtr(static_cast<const Subclass*>(this));
}
/**
* Attempts to acquire the lock in shared mode. If acquisition is
* unsuccessful, the returned LockedPtr will be null.
*
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
* validity.)
*/
TryRLockedPtr tryRLock() const {
return TryRLockedPtr{static_cast<const Subclass*>(this)};
}
/**
* Attempts to acquire the lock, or fails if the timeout elapses first.
* If acquisition is unsuccessful, the returned LockedPtr will be null.
*
* (Use LockedPtr::isNull() to check for validity.)
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
* validity.)
*/
template <class Rep, class Period>
LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) {
......@@ -127,7 +161,8 @@ class SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
* Attempts to acquire the lock, or fails if the timeout elapses first.
* If acquisition is unsuccessful, the returned LockedPtr will be null.
*
* (Use LockedPtr::isNull() to check for validity.)
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
* validity.)
*/
template <class Rep, class Period>
ConstLockedPtr rlock(
......@@ -224,6 +259,11 @@ class SynchronizedBase<Subclass, detail::MutexLevel::UPGRADE>
using ConstUpgradeLockedGuardPtr =
::folly::LockedGuardPtr<const Subclass, LockPolicyUpgrade>;
using TryUpgradeLockedPtr =
::folly::LockedPtr<Subclass, LockPolicyTryUpgrade>;
using ConstTryUpgradeLockedPtr =
::folly::LockedPtr<const Subclass, LockPolicyTryUpgrade>;
/**
* Acquire an upgrade lock and return a LockedPtr that can be used to safely
* access the datum
......@@ -237,6 +277,20 @@ class SynchronizedBase<Subclass, detail::MutexLevel::UPGRADE>
return ConstUpgradeLockedPtr(static_cast<const Subclass*>(this));
}
/**
* Attempts to acquire the lock in upgrade mode. If acquisition is
* unsuccessful, the returned LockedPtr will be null.
*
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
* validity.)
*/
TryUpgradeLockedPtr tryULock() {
return TryUpgradeLockedPtr{static_cast<Subclass*>(this)};
}
ConstTryUpgradeLockedPtr tryULock() const {
return ConstTryUpgradeLockedPtr{static_cast<const Subclass*>(this)};
}
/**
* Acquire an upgrade lock and return a LockedPtr that can be used to safely
* access the datum
......@@ -314,6 +368,10 @@ class SynchronizedBase<Subclass, detail::MutexLevel::UNIQUE> {
using ConstLockedPtr =
::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
using TryLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyTryExclusive>;
using ConstTryLockedPtr =
::folly::LockedPtr<const Subclass, LockPolicyTryExclusive>;
/**
* Acquire a lock, and return a LockedPtr that can be used to safely access
* the datum.
......@@ -330,6 +388,20 @@ class SynchronizedBase<Subclass, detail::MutexLevel::UNIQUE> {
return ConstLockedPtr(static_cast<const Subclass*>(this));
}
/**
* Attempts to acquire the lock in exclusive mode. If acquisition is
* unsuccessful, the returned LockedPtr will be null.
*
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
* validity.)
*/
TryLockedPtr tryLock() {
return TryLockedPtr{static_cast<Subclass*>(this)};
}
ConstTryLockedPtr tryLock() const {
return ConstTryLockedPtr{static_cast<const Subclass*>(this)};
}
/**
* Attempts to acquire the lock, or fails if the timeout elapses first.
* If acquisition is unsuccessful, the returned LockedPtr will be null.
......@@ -849,7 +921,10 @@ class LockedPtrBase {
protected:
LockedPtrBase() {}
explicit LockedPtrBase(SynchronizedType* parent) : parent_(parent) {
LockPolicy::lock(parent_->mutex_);
DCHECK(parent);
if (!LockPolicy::lock(parent_->mutex_)) {
parent_ = nullptr;
}
}
template <class Rep, class Period>
LockedPtrBase(
......@@ -964,7 +1039,13 @@ class LockedPtrBase<SynchronizedType, std::mutex, LockPolicy> {
protected:
LockedPtrBase() {}
explicit LockedPtrBase(SynchronizedType* parent)
: lock_(parent->mutex_), parent_(parent) {}
: lock_{parent->mutex_, std::adopt_lock}, parent_{parent} {
DCHECK(parent);
if (!LockPolicy::lock(parent_->mutex_)) {
parent_ = nullptr;
lock_.release();
}
}
using UnlockerData =
std::pair<std::unique_lock<std::mutex>, SynchronizedType*>;
......
......@@ -18,6 +18,7 @@
// Test bed for folly/Synchronized.h
#include <folly/Synchronized.h>
#include <folly/Function.h>
#include <folly/LockTraitsBoost.h>
#include <folly/Portability.h>
#include <folly/SharedMutex.h>
......@@ -28,6 +29,8 @@
using namespace folly::sync_tests;
namespace folly {
template <class Mutex>
class SynchronizedTest : public testing::Test {};
......@@ -544,3 +547,145 @@ TEST_F(SynchronizedLockTest, TestPieceWiseConstruct) {
EXPECT_EQ(*synchronized.lock(), 3);
EXPECT_EQ(NonDefaultConstructibleMutex::value, 1);
}
namespace {
constexpr auto kLockable = 1;
constexpr auto kWLockable = 2;
constexpr auto kRLockable = 4;
constexpr auto kULockable = 8;
template <int kLockableType>
class TryLockable {
public:
explicit TryLockable(
bool shouldSucceed,
folly::Function<void()> onLockIn,
folly::Function<void()> onUnlockIn)
: kShouldSucceed{shouldSucceed},
onLock{std::move(onLockIn)},
onUnlock{std::move(onUnlockIn)} {}
void lock() {
EXPECT_TRUE(false);
}
template <
int LockableType = kLockableType,
std::enable_if_t<LockableType != kLockable>* = nullptr>
void lock_shared() {
EXPECT_TRUE(false);
}
template <
int LockableType = kLockableType,
std::enable_if_t<LockableType == kULockable>* = nullptr>
void lock_upgrade() {
EXPECT_TRUE(false);
}
bool tryLockImpl(int lockableMask) {
// if the lockable type of this instance is one of the possible options as
// expressed in the mask go through the usual test code
if (kLockableType | lockableMask) {
if (kShouldSucceed) {
onLock();
return true;
} else {
return false;
}
}
// else fail the test
EXPECT_TRUE(false);
return false;
}
void unlockImpl(int lockableMask) {
if (kLockableType | lockableMask) {
onUnlock();
return;
}
EXPECT_TRUE(false);
}
bool try_lock() {
return tryLockImpl(kLockable | kWLockable);
}
bool try_lock_shared() {
return tryLockImpl(kRLockable);
}
bool try_lock_upgrade() {
return tryLockImpl(kULockable);
}
void unlock() {
unlockImpl(kLockable | kWLockable);
}
void unlock_shared() {
unlockImpl(kLockable | kRLockable);
}
void unlock_upgrade() {
unlockImpl(kLockable | kULockable);
}
const bool kShouldSucceed;
folly::Function<void()> onLock;
folly::Function<void()> onUnlock;
};
template <int kLockable, typename Func>
void testTryLock(Func func) {
{
auto locked = 0;
auto unlocked = 0;
folly::Synchronized<int, TryLockable<kLockable>> synchronized{
std::piecewise_construct,
std::make_tuple(),
std::make_tuple(true, [&] { ++locked; }, [&] { ++unlocked; })};
{
auto lock = func(synchronized);
EXPECT_TRUE(lock);
EXPECT_EQ(locked, 1);
}
EXPECT_EQ(locked, 1);
EXPECT_EQ(unlocked, 1);
}
{
auto locked = 0;
auto unlocked = 0;
folly::Synchronized<int, TryLockable<kLockable>> synchronized{
std::piecewise_construct,
std::make_tuple(),
std::make_tuple(false, [&] { ++locked; }, [&] { ++unlocked; })};
{
auto lock = func(synchronized);
EXPECT_FALSE(lock);
EXPECT_EQ(locked, 0);
}
EXPECT_EQ(locked, 0);
EXPECT_EQ(unlocked, 0);
}
}
} // namespace
TEST_F(SynchronizedLockTest, TestTryLock) {
testTryLock<kLockable>(
[](auto& synchronized) { return synchronized.tryLock(); });
}
TEST_F(SynchronizedLockTest, TestTryWLock) {
testTryLock<kWLockable>(
[](auto& synchronized) { return synchronized.tryWLock(); });
}
TEST_F(SynchronizedLockTest, TestTryRLock) {
testTryLock<kRLockable>(
[](auto& synchronized) { return synchronized.tryRLock(); });
}
TEST_F(SynchronizedLockTest, TestTryULock) {
testTryLock<kULockable>(
[](auto& synchronized) { return synchronized.tryULock(); });
}
} // namespace folly
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment