#pragma once
+#include <folly/Likely.h>
#include <folly/LockTraits.h>
#include <folly/Preprocessor.h>
#include <folly/SharedMutex.h>
class LockedPtrBase;
template <class LockedType, class LockPolicy>
class LockedPtr;
+template <class LockedType, class LockPolicy = LockPolicyExclusive>
+class LockedGuardPtr;
+
+/**
+ * Public version of LockInterfaceDispatcher that contains the MutexLevel enum
+ * for the passed in mutex type
+ *
+ * This is decoupled from MutexLevelValueImpl in LockTraits.h because this
+ * ensures that a heterogenous mutex with a different API can be used. For
+ * example - if a mutex does not have a lock_shared() method but the
+ * LockTraits specialization for it supports a static non member
+ * lock_shared(Mutex&) it can be used as a shared mutex and will provide
+ * rlock() and wlock() functions.
+ */
+template <class Mutex>
+using MutexLevelValue = detail::MutexLevelValueImpl<
+ true,
+ LockTraits<Mutex>::is_shared,
+ LockTraits<Mutex>::is_upgrade>;
/**
* SynchronizedBase is a helper parent class for Synchronized<T>.
* It provides wlock() and rlock() methods for shared mutex types,
* or lock() methods for purely exclusive mutex types.
*/
-template <class Subclass, bool is_shared>
+template <class Subclass, detail::MutexLevel level>
class SynchronizedBase;
/**
* accessing the data.
*/
template <class Subclass>
-class SynchronizedBase<Subclass, true> {
+class SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
public:
using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
using ConstWLockedPtr =
const std::chrono::duration<Rep, Period>& timeout) const {
return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
}
+
+ /*
+ * Note: C++ 17 adds guaranteed copy elision. (http://wg21.link/P0135)
+ * Once compilers support this, it would be nice to add wguard() and rguard()
+ * methods that return LockedGuardPtr objects.
+ */
+
+ /**
+ * Invoke a function while holding the lock exclusively.
+ *
+ * A reference to the datum will be passed into the function as its only
+ * argument.
+ *
+ * This can be used with a lambda argument for easily defining small critical
+ * sections in the code. For example:
+ *
+ * auto value = obj.withWLock([](auto& data) {
+ * data.doStuff();
+ * return data.getValue();
+ * });
+ */
+ template <class Function>
+ auto withWLock(Function&& function) {
+ LockedGuardPtr<Subclass, LockPolicyExclusive> guardPtr(
+ static_cast<Subclass*>(this));
+ return function(*guardPtr);
+ }
+ template <class Function>
+ auto withWLock(Function&& function) const {
+ LockedGuardPtr<const Subclass, LockPolicyExclusive> guardPtr(
+ static_cast<const Subclass*>(this));
+ return function(*guardPtr);
+ }
+
+ /**
+ * Invoke a function while holding the lock exclusively.
+ *
+ * This is similar to withWLock(), but the function will be passed a
+ * LockedPtr rather than a reference to the data itself.
+ *
+ * This allows scopedUnlock() to be called on the LockedPtr argument if
+ * desired.
+ */
+ template <class Function>
+ auto withWLockPtr(Function&& function) {
+ return function(wlock());
+ }
+ template <class Function>
+ auto withWLockPtr(Function&& function) const {
+ return function(wlock());
+ }
+
+ /**
+ * Invoke a function while holding an the lock in shared mode.
+ *
+ * A const reference to the datum will be passed into the function as its
+ * only argument.
+ */
+ template <class Function>
+ auto withRLock(Function&& function) const {
+ LockedGuardPtr<const Subclass, LockPolicyShared> guardPtr(
+ static_cast<const Subclass*>(this));
+ return function(*guardPtr);
+ }
+
+ template <class Function>
+ auto withRLockPtr(Function&& function) const {
+ return function(rlock());
+ }
+};
+
+/**
+ * SynchronizedBase specialization for upgrade mutex types.
+ *
+ * This class provides all the functionality provided by the SynchronizedBase
+ * specialization for shared mutexes and a ulock() method that returns an
+ * upgradable lock RAII proxy
+ */
+template <class Subclass>
+class SynchronizedBase<Subclass, detail::MutexLevel::UPGRADE>
+ : public SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
+ public:
+ using UpgradeLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyUpgrade>;
+ using ConstUpgradeLockedPtr =
+ ::folly::LockedPtr<const Subclass, LockPolicyUpgrade>;
+ using UpgradeLockedGuardPtr =
+ ::folly::LockedGuardPtr<Subclass, LockPolicyUpgrade>;
+ using ConstUpgradeLockedGuardPtr =
+ ::folly::LockedGuardPtr<const Subclass, LockPolicyUpgrade>;
+
+ /**
+ * Acquire an upgrade lock and return a LockedPtr that can be used to safely
+ * access the datum
+ *
+ * And the const version
+ */
+ UpgradeLockedPtr ulock() {
+ return UpgradeLockedPtr(static_cast<Subclass*>(this));
+ }
+ ConstUpgradeLockedPtr ulock() const {
+ return ConstUpgradeLockedPtr(static_cast<const Subclass*>(this));
+ }
+
+ /**
+ * Acquire an upgrade lock and return a LockedPtr that can be used to safely
+ * access the datum
+ *
+ * And the const version
+ */
+ template <class Rep, class Period>
+ UpgradeLockedPtr ulock(const std::chrono::duration<Rep, Period>& timeout) {
+ return UpgradeLockedPtr(static_cast<Subclass*>(this), timeout);
+ }
+ template <class Rep, class Period>
+ UpgradeLockedPtr ulock(
+ const std::chrono::duration<Rep, Period>& timeout) const {
+ return ConstUpgradeLockedPtr(static_cast<const Subclass*>(this), timeout);
+ }
+
+ /**
+ * Invoke a function while holding the lock.
+ *
+ * A reference to the datum will be passed into the function as its only
+ * argument.
+ *
+ * This can be used with a lambda argument for easily defining small critical
+ * sections in the code. For example:
+ *
+ * auto value = obj.withULock([](auto& data) {
+ * data.doStuff();
+ * return data.getValue();
+ * });
+ *
+ * This is probably not the function you want. If the intent is to read the
+ * data object and determine whether you should upgrade to a write lock then
+ * the withULockPtr() method should be called instead, since it gives access
+ * to the LockedPtr proxy (which can be upgraded via the
+ * moveFromUpgradeToWrite() method)
+ */
+ template <class Function>
+ auto withULock(Function&& function) const {
+ ConstUpgradeLockedGuardPtr guardPtr(static_cast<const Subclass*>(this));
+ return function(*guardPtr);
+ }
+
+ /**
+ * Invoke a function while holding the lock exclusively.
+ *
+ * This is similar to withULock(), but the function will be passed a
+ * LockedPtr rather than a reference to the data itself.
+ *
+ * This allows scopedUnlock() and getUniqueLock() to be called on the
+ * LockedPtr argument.
+ *
+ * This also allows you to upgrade the LockedPtr proxy to a write state so
+ * that changes can be made to the underlying data
+ */
+ template <class Function>
+ auto withULockPtr(Function&& function) {
+ return function(ulock());
+ }
+ template <class Function>
+ auto withULockPtr(Function&& function) const {
+ return function(ulock());
+ }
};
/**
* data.
*/
template <class Subclass>
-class SynchronizedBase<Subclass, false> {
+class SynchronizedBase<Subclass, detail::MutexLevel::UNIQUE> {
public:
using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
using ConstLockedPtr =
ConstLockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) const {
return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
}
+
+ /*
+ * Note: C++ 17 adds guaranteed copy elision. (http://wg21.link/P0135)
+ * Once compilers support this, it would be nice to add guard() methods that
+ * return LockedGuardPtr objects.
+ */
+
+ /**
+ * Invoke a function while holding the lock.
+ *
+ * A reference to the datum will be passed into the function as its only
+ * argument.
+ *
+ * This can be used with a lambda argument for easily defining small critical
+ * sections in the code. For example:
+ *
+ * auto value = obj.withLock([](auto& data) {
+ * data.doStuff();
+ * return data.getValue();
+ * });
+ */
+ template <class Function>
+ auto withLock(Function&& function) {
+ LockedGuardPtr<Subclass, LockPolicyExclusive> guardPtr(
+ static_cast<Subclass*>(this));
+ return function(*guardPtr);
+ }
+ template <class Function>
+ auto withLock(Function&& function) const {
+ LockedGuardPtr<const Subclass, LockPolicyExclusive> guardPtr(
+ static_cast<const Subclass*>(this));
+ return function(*guardPtr);
+ }
+
+ /**
+ * Invoke a function while holding the lock exclusively.
+ *
+ * This is similar to withWLock(), but the function will be passed a
+ * LockedPtr rather than a reference to the data itself.
+ *
+ * This allows scopedUnlock() and getUniqueLock() to be called on the
+ * LockedPtr argument.
+ */
+ template <class Function>
+ auto withLockPtr(Function&& function) {
+ return function(lock());
+ }
+ template <class Function>
+ auto withLockPtr(Function&& function) const {
+ return function(lock());
+ }
};
/**
template <class T, class Mutex = SharedMutex>
struct Synchronized : public SynchronizedBase<
Synchronized<T, Mutex>,
- LockTraits<Mutex>::is_shared> {
+ MutexLevelValue<Mutex>::value> {
private:
using Base =
- SynchronizedBase<Synchronized<T, Mutex>, LockTraits<Mutex>::is_shared>;
+ SynchronizedBase<Synchronized<T, Mutex>, MutexLevelValue<Mutex>::value>;
static constexpr bool nxCopyCtor{
std::is_nothrow_copy_constructible<T>::value};
static constexpr bool nxMoveCtor{
* Copy constructor copies the data (with locking the source and
* all) but does NOT copy the mutex. Doing so would result in
* deadlocks.
+ *
+ * Note that the copy constructor may throw because it acquires a lock in
+ * the contextualRLock() method
*/
- Synchronized(const Synchronized& rhs) noexcept(nxCopyCtor)
+ Synchronized(const Synchronized& rhs) /* may throw */
: Synchronized(rhs, rhs.contextualRLock()) {}
/**
* Move constructor moves the data (with locking the source and all)
* but does not move the mutex.
+ *
+ * Note that the move constructor may throw because it acquires a lock.
+ * Since the move constructor is not declared noexcept, when objects of this
+ * class are used as elements in a vector or a similar container. The
+ * elements might not be moved around when resizing. They might be copied
+ * instead. You have been warned.
*/
- Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor)
+ Synchronized(Synchronized&& rhs) /* may throw */
: Synchronized(std::move(rhs), rhs.contextualLock()) {}
/**
friend class folly::LockedPtrBase;
template <class LockedType, class LockPolicy>
friend class folly::LockedPtr;
+ template <class LockedType, class LockPolicy>
+ friend class folly::LockedGuardPtr;
/**
* Helper constructors to enable Synchronized for
template <class SynchronizedType, class LockPolicy>
class ScopedUnlocker;
+namespace detail {
+/*
+ * A helper alias that resolves to "const T" if the template parameter
+ * is a const Synchronized<T>, or "T" if the parameter is not const.
+ */
+template <class SynchronizedType>
+using SynchronizedDataType = typename std::conditional<
+ std::is_const<SynchronizedType>::value,
+ typename SynchronizedType::DataType const,
+ typename SynchronizedType::DataType>::type;
+/*
+ * A helper alias that resolves to a ConstLockedPtr if the template parameter
+ * is a const Synchronized<T>, or a LockedPtr if the parameter is not const.
+ */
+template <class SynchronizedType>
+using LockedPtrType = typename std::conditional<
+ std::is_const<SynchronizedType>::value,
+ typename SynchronizedType::ConstLockedPtr,
+ typename SynchronizedType::LockedPtr>::type;
+} // detail
+
/**
* A helper base class for implementing LockedPtr.
*
}
}
+ /**
+ * Unlock the synchronized data.
+ *
+ * The LockedPtr can no longer be dereferenced after unlock() has been
+ * called. isValid() will return false on an unlocked LockedPtr.
+ *
+ * unlock() can only be called on a LockedPtr that is valid.
+ */
+ void unlock() {
+ DCHECK(parent_ != nullptr);
+ LockPolicy::unlock(parent_->mutex_);
+ parent_ = nullptr;
+ }
+
protected:
LockedPtrBase() {}
explicit LockedPtrBase(SynchronizedType* parent) : parent_(parent) {
}
UnlockerData releaseLock() {
+ DCHECK(parent_ != nullptr);
auto current = parent_;
parent_ = nullptr;
LockPolicy::unlock(current->mutex_);
return lock_;
}
+ /**
+ * Unlock the synchronized data.
+ *
+ * The LockedPtr can no longer be dereferenced after unlock() has been
+ * called. isValid() will return false on an unlocked LockedPtr.
+ *
+ * unlock() can only be called on a LockedPtr that is valid.
+ */
+ void unlock() {
+ DCHECK(parent_ != nullptr);
+ lock_.unlock();
+ parent_ = nullptr;
+ }
+
protected:
LockedPtrBase() {}
explicit LockedPtrBase(SynchronizedType* parent)
}
UnlockerData releaseLock() {
+ DCHECK(parent_ != nullptr);
UnlockerData data(std::move(lock_), parent_);
parent_ = nullptr;
data.first.unlock();
LockPolicy>;
using UnlockerData = typename Base::UnlockerData;
// CDataType is the DataType with the appropriate const-qualification
- using CDataType = typename std::conditional<
- std::is_const<SynchronizedType>::value,
- typename SynchronizedType::DataType const,
- typename SynchronizedType::DataType>::type;
+ using CDataType = detail::SynchronizedDataType<SynchronizedType>;
public:
using DataType = typename SynchronizedType::DataType;
ScopedUnlocker<SynchronizedType, LockPolicy> scopedUnlock() {
return ScopedUnlocker<SynchronizedType, LockPolicy>(this);
}
+
+ /***************************************************************************
+ * Upgradable lock methods.
+ * These are disabled via SFINAE when the mutex is not upgradable
+ **************************************************************************/
+ /**
+ * Move the locked ptr from an upgrade state to an exclusive state. The
+ * current lock is left in a null state.
+ */
+ template <
+ typename SyncType = SynchronizedType,
+ typename = typename std::enable_if<
+ LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
+ LockedPtr<SynchronizedType, LockPolicyFromUpgradeToExclusive>
+ moveFromUpgradeToWrite() {
+ auto* parent_to_pass_on = this->parent_;
+ this->parent_ = nullptr;
+ return LockedPtr<SynchronizedType, LockPolicyFromUpgradeToExclusive>(
+ parent_to_pass_on);
+ }
+
+ /**
+ * Move the locked ptr from an exclusive state to an upgrade state. The
+ * current lock is left in a null state.
+ */
+ template <
+ typename SyncType = SynchronizedType,
+ typename = typename std::enable_if<
+ LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
+ LockedPtr<SynchronizedType, LockPolicyFromExclusiveToUpgrade>
+ moveFromWriteToUpgrade() {
+ auto* parent_to_pass_on = this->parent_;
+ this->parent_ = nullptr;
+ return LockedPtr<SynchronizedType, LockPolicyFromExclusiveToUpgrade>(
+ parent_to_pass_on);
+ }
+
+ /**
+ * Move the locked ptr from an upgrade state to a shared state. The
+ * current lock is left in a null state.
+ */
+ template <
+ typename SyncType = SynchronizedType,
+ typename = typename std::enable_if<
+ LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
+ LockedPtr<SynchronizedType, LockPolicyFromUpgradeToShared>
+ moveFromUpgradeToRead() {
+ auto* parent_to_pass_on = this->parent_;
+ this->parent_ = nullptr;
+ return LockedPtr<SynchronizedType, LockPolicyFromUpgradeToShared>(
+ parent_to_pass_on);
+ }
+
+ /**
+ * Move the locked ptr from an exclusive state to a shared state. The
+ * current lock is left in a null state.
+ */
+ template <
+ typename SyncType = SynchronizedType,
+ typename = typename std::enable_if<
+ LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
+ LockedPtr<SynchronizedType, LockPolicyFromExclusiveToShared>
+ moveFromWriteToRead() {
+ auto* parent_to_pass_on = this->parent_;
+ this->parent_ = nullptr;
+ return LockedPtr<SynchronizedType, LockPolicyFromExclusiveToShared>(
+ parent_to_pass_on);
+ }
};
-namespace detail {
-/*
- * A helper alias to select a ConstLockedPtr if the template parameter is a
- * const Synchronized<T>, or a LockedPtr if the parameter is not const.
+/**
+ * LockedGuardPtr is a simplified version of LockedPtr.
+ *
+ * It is non-movable, and supports fewer features than LockedPtr. However, it
+ * is ever-so-slightly more performant than LockedPtr. (The destructor can
+ * unconditionally release the lock, without requiring a conditional branch.)
+ *
+ * The relationship between LockedGuardPtr and LockedPtr is similar to that
+ * between std::lock_guard and std::unique_lock.
*/
-template <class SynchronizedType>
-using LockedPtrType = typename std::conditional<
- std::is_const<SynchronizedType>::value,
- typename SynchronizedType::ConstLockedPtr,
- typename SynchronizedType::LockedPtr>::type;
-} // detail
+template <class SynchronizedType, class LockPolicy>
+class LockedGuardPtr {
+ private:
+ // CDataType is the DataType with the appropriate const-qualification
+ using CDataType = detail::SynchronizedDataType<SynchronizedType>;
+
+ public:
+ using DataType = typename SynchronizedType::DataType;
+ using MutexType = typename SynchronizedType::MutexType;
+ using Synchronized = typename std::remove_const<SynchronizedType>::type;
+
+ LockedGuardPtr() = delete;
+
+ /**
+ * Takes a Synchronized<T> and locks it.
+ */
+ explicit LockedGuardPtr(SynchronizedType* parent) : parent_(parent) {
+ LockPolicy::lock(parent_->mutex_);
+ }
+
+ /**
+ * Destructor releases.
+ */
+ ~LockedGuardPtr() {
+ LockPolicy::unlock(parent_->mutex_);
+ }
+
+ /**
+ * Access the locked data.
+ */
+ CDataType* operator->() const {
+ return &parent_->datum_;
+ }
+
+ /**
+ * Access the locked data.
+ */
+ CDataType& operator*() const {
+ return parent_->datum_;
+ }
+
+ private:
+ // This is the entire state of LockedGuardPtr.
+ SynchronizedType* const parent_{nullptr};
+};
/**
* Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
#define SYNCHRONIZED(...) \
FOLLY_PUSH_WARNING \
FOLLY_GCC_DISABLE_WARNING(shadow) \
+ FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS \
if (bool SYNCHRONIZED_state = false) { \
} else \
for (auto SYNCHRONIZED_lockedPtr = \