blob: 2054aa06db0306692a17a1ec0cbd3e39e57d8076 [file] [log] [blame]
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This module implements a Synchronized abstraction useful in
* mutex-based concurrency.
*
* The Synchronized<T, Mutex> class is the primary public API exposed by this
* module. See folly/docs/Synchronized.md for a more complete explanation of
* this class and its benefits.
*/
#pragma once
#include <folly/Likely.h>
#include <folly/LockTraits.h>
#include <folly/Preprocessor.h>
#include <folly/SharedMutex.h>
#include <folly/Traits.h>
#include <glog/logging.h>
#include <mutex>
#include <type_traits>
namespace folly {
template <class LockedType, class Mutex, class LockPolicy>
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, detail::MutexLevel level>
class SynchronizedBase;
/**
* SynchronizedBase specialization for shared mutex types.
*
* This class provides wlock() and rlock() methods for acquiring the lock and
* accessing the data.
*/
template <class Subclass>
class SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
public:
using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
using ConstWLockedPtr =
::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
using ConstLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyShared>;
/**
* Acquire an exclusive lock, and return a LockedPtr that can be used to
* safely access the datum.
*
* LockedPtr offers operator -> and * to provide access to the datum.
* The lock will be released when the LockedPtr is destroyed.
*/
LockedPtr wlock() {
return LockedPtr(static_cast<Subclass*>(this));
}
ConstWLockedPtr wlock() const {
return ConstWLockedPtr(static_cast<const Subclass*>(this));
}
/**
* Acquire a read lock, and return a ConstLockedPtr that can be used to
* safely access the datum.
*/
ConstLockedPtr rlock() const {
return ConstLockedPtr(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.)
*/
template <class Rep, class Period>
LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) {
return LockedPtr(static_cast<Subclass*>(this), timeout);
}
template <class Rep, class Period>
ConstWLockedPtr wlock(
const std::chrono::duration<Rep, Period>& timeout) const {
return ConstWLockedPtr(static_cast<const Subclass*>(this), timeout);
}
/**
* 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.)
*/
template <class Rep, class Period>
ConstLockedPtr rlock(
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());
}
};
/**
* SynchronizedBase specialization for non-shared mutex types.
*
* This class provides lock() methods for acquiring the lock and accessing the
* data.
*/
template <class Subclass>
class SynchronizedBase<Subclass, detail::MutexLevel::UNIQUE> {
public:
using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
using ConstLockedPtr =
::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
/**
* Acquire a lock, and return a LockedPtr that can be used to safely access
* the datum.
*/
LockedPtr lock() {
return LockedPtr(static_cast<Subclass*>(this));
}
/**
* Acquire a lock, and return a ConstLockedPtr that can be used to safely
* access the datum.
*/
ConstLockedPtr lock() const {
return ConstLockedPtr(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.
*/
template <class Rep, class Period>
LockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) {
return LockedPtr(static_cast<Subclass*>(this), timeout);
}
/**
* Attempts to acquire the lock, or fails if the timeout elapses first.
* If acquisition is unsuccessful, the returned LockedPtr will be null.
*/
template <class Rep, class Period>
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());
}
};
/**
* Synchronized<T> encapsulates an object of type T (a "datum") paired
* with a mutex. The only way to access the datum is while the mutex
* is locked, and Synchronized makes it virtually impossible to do
* otherwise. The code that would access the datum in unsafe ways
* would look odd and convoluted, thus readily alerting the human
* reviewer. In contrast, the code that uses Synchronized<T> correctly
* looks simple and intuitive.
*
* The second parameter must be a mutex type. Any mutex type supported by
* LockTraits<Mutex> can be used. By default any class with lock() and
* unlock() methods will work automatically. LockTraits can be specialized to
* teach Synchronized how to use other custom mutex types. See the
* documentation in LockTraits.h for additional details.
*
* Supported mutexes that work by default include std::mutex,
* std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex,
* folly::SharedMutex, folly::RWSpinLock, and folly::SpinLock.
* Include LockTraitsBoost.h to get additional LockTraits specializations to
* support the following boost mutex types: boost::mutex,
* boost::recursive_mutex, boost::shared_mutex, boost::timed_mutex, and
* boost::recursive_timed_mutex.
*/
template <class T, class Mutex = SharedMutex>
struct Synchronized : public SynchronizedBase<
Synchronized<T, Mutex>,
MutexLevelValue<Mutex>::value> {
private:
using Base =
SynchronizedBase<Synchronized<T, Mutex>, MutexLevelValue<Mutex>::value>;
static constexpr bool nxCopyCtor{
std::is_nothrow_copy_constructible<T>::value};
static constexpr bool nxMoveCtor{
std::is_nothrow_move_constructible<T>::value};
// used to disable copy construction and assignment
class NonImplementedType;
public:
using LockedPtr = typename Base::LockedPtr;
using ConstLockedPtr = typename Base::ConstLockedPtr;
using DataType = T;
using MutexType = Mutex;
/**
* Default constructor leaves both members call their own default
* constructor.
*/
Synchronized() = default;
/**
* 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
*/
public:
/* implicit */ Synchronized(typename std::conditional<
std::is_copy_constructible<T>::value,
const Synchronized&,
NonImplementedType>::type 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) /* may throw */
: Synchronized(std::move(rhs), rhs.contextualLock()) {}
/**
* Constructor taking a datum as argument copies it. There is no
* need to lock the constructing object.
*/
explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
/**
* Constructor taking a datum rvalue as argument moves it. Again,
* there is no need to lock the constructing object.
*/
explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
: datum_(std::move(rhs)) {}
/**
* Lets you construct non-movable types in-place. Use the constexpr
* instance `in_place` as the first argument.
*/
template <typename... Args>
explicit Synchronized(in_place_t, Args&&... args)
: datum_(std::forward<Args>(args)...) {}
/**
* The canonical assignment operator only assigns the data, NOT the
* mutex. It locks the two objects in ascending order of their
* addresses.
*/
Synchronized& operator=(typename std::conditional<
std::is_copy_assignable<T>::value,
const Synchronized&,
NonImplementedType>::type rhs) {
if (this == &rhs) {
// Self-assignment, pass.
} else if (this < &rhs) {
auto guard1 = operator->();
auto guard2 = rhs.operator->();
datum_ = rhs.datum_;
} else {
auto guard1 = rhs.operator->();
auto guard2 = operator->();
datum_ = rhs.datum_;
}
return *this;
}
/**
* Move assignment operator, only assigns the data, NOT the
* mutex. It locks the two objects in ascending order of their
* addresses.
*/
Synchronized& operator=(Synchronized&& rhs) {
if (this == &rhs) {
// Self-assignment, pass.
} else if (this < &rhs) {
auto guard1 = operator->();
auto guard2 = rhs.operator->();
datum_ = std::move(rhs.datum_);
} else {
auto guard1 = rhs.operator->();
auto guard2 = operator->();
datum_ = std::move(rhs.datum_);
}
return *this;
}
/**
* Lock object, assign datum.
*/
Synchronized& operator=(const T& rhs) {
auto guard = operator->();
datum_ = rhs;
return *this;
}
/**
* Lock object, move-assign datum.
*/
Synchronized& operator=(T&& rhs) {
auto guard = operator->();
datum_ = std::move(rhs);
return *this;
}
/**
* Acquire an appropriate lock based on the context.
*
* If the mutex is a shared mutex, and the Synchronized instance is const,
* this acquires a shared lock. Otherwise this acquires an exclusive lock.
*
* In general, prefer using the explicit rlock() and wlock() methods
* for read-write locks, and lock() for purely exclusive locks.
*
* contextualLock() is primarily intended for use in other template functions
* that do not necessarily know the lock type.
*/
LockedPtr contextualLock() {
return LockedPtr(this);
}
ConstLockedPtr contextualLock() const {
return ConstLockedPtr(this);
}
template <class Rep, class Period>
LockedPtr contextualLock(const std::chrono::duration<Rep, Period>& timeout) {
return LockedPtr(this, timeout);
}
template <class Rep, class Period>
ConstLockedPtr contextualLock(
const std::chrono::duration<Rep, Period>& timeout) const {
return ConstLockedPtr(this, timeout);
}
/**
* contextualRLock() acquires a read lock if the mutex type is shared,
* or a regular exclusive lock for non-shared mutex types.
*
* contextualRLock() when you know that you prefer a read lock (if
* available), even if the Synchronized<T> object itself is non-const.
*/
ConstLockedPtr contextualRLock() const {
return ConstLockedPtr(this);
}
template <class Rep, class Period>
ConstLockedPtr contextualRLock(
const std::chrono::duration<Rep, Period>& timeout) const {
return ConstLockedPtr(this, timeout);
}
/**
* This accessor offers a LockedPtr. In turn, LockedPtr offers
* operator-> returning a pointer to T. The operator-> keeps
* expanding until it reaches a pointer, so syncobj->foo() will lock
* the object and call foo() against it.
*
* NOTE: This API is planned to be deprecated in an upcoming diff.
* Prefer using lock(), wlock(), or rlock() instead.
*/
LockedPtr operator->() {
return LockedPtr(this);
}
/**
* Obtain a ConstLockedPtr.
*
* NOTE: This API is planned to be deprecated in an upcoming diff.
* Prefer using lock(), wlock(), or rlock() instead.
*/
ConstLockedPtr operator->() const {
return ConstLockedPtr(this);
}
/**
* Attempts to acquire for a given number of milliseconds. If
* acquisition is unsuccessful, the returned LockedPtr is nullptr.
*
* NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead.
* In the future it will be marked with a deprecation attribute to emit
* build-time warnings, and then it will be removed entirely.
*/
LockedPtr timedAcquire(unsigned int milliseconds) {
return LockedPtr(this, std::chrono::milliseconds(milliseconds));
}
/**
* Attempts to acquire for a given number of milliseconds. If
* acquisition is unsuccessful, the returned ConstLockedPtr is nullptr.
*
* NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead.
* In the future it will be marked with a deprecation attribute to emit
* build-time warnings, and then it will be removed entirely.
*/
ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
return ConstLockedPtr(this, std::chrono::milliseconds(milliseconds));
}
/**
* Sometimes, although you have a mutable object, you only want to
* call a const method against it. The most efficient way to achieve
* that is by using a read lock. You get to do so by using
* obj.asConst()->method() instead of obj->method().
*
* NOTE: This API is planned to be deprecated in an upcoming diff.
* Use rlock() instead.
*/
const Synchronized& asConst() const {
return *this;
}
/**
* Swaps with another Synchronized. Protected against
* self-swap. Only data is swapped. Locks are acquired in increasing
* address order.
*/
void swap(Synchronized& rhs) {
if (this == &rhs) {
return;
}
if (this > &rhs) {
return rhs.swap(*this);
}
auto guard1 = operator->();
auto guard2 = rhs.operator->();
using std::swap;
swap(datum_, rhs.datum_);
}
/**
* Swap with another datum. Recommended because it keeps the mutex
* held only briefly.
*/
void swap(T& rhs) {
LockedPtr guard(this);
using std::swap;
swap(datum_, rhs);
}
/**
* Copies datum to a given target.
*/
void copy(T* target) const {
ConstLockedPtr guard(this);
*target = datum_;
}
/**
* Returns a fresh copy of the datum.
*/
T copy() const {
ConstLockedPtr guard(this);
return datum_;
}
private:
template <class LockedType, class MutexType, class LockPolicy>
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
* non-default constructible types T.
* Guards are created in actual public constructors and are alive
* for the time required to construct the object
*/
Synchronized(
const Synchronized& rhs,
const ConstLockedPtr& /*guard*/) noexcept(nxCopyCtor)
: datum_(rhs.datum_) {}
Synchronized(Synchronized&& rhs, const LockedPtr& /*guard*/) noexcept(
nxMoveCtor)
: datum_(std::move(rhs.datum_)) {}
// Synchronized data members
T datum_;
mutable Mutex mutex_;
};
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.
*
* The main reason for having this as a separate class is so we can specialize
* it for std::mutex, so we can expose a std::unique_lock to the caller
* when std::mutex is being used. This allows callers to use a
* std::condition_variable with the mutex from a Synchronized<T, std::mutex>.
*
* We don't use std::unique_lock with other Mutex types since it makes the
* LockedPtr class slightly larger, and it makes the logic to support
* ScopedUnlocker slightly more complicated. std::mutex is the only one that
* really seems to benefit from the unique_lock. std::condition_variable
* itself only supports std::unique_lock<std::mutex>, so there doesn't seem to
* be any real benefit to exposing the unique_lock with other mutex types.
*
* Note that the SynchronizedType template parameter may or may not be const
* qualified.
*/
template <class SynchronizedType, class Mutex, class LockPolicy>
class LockedPtrBase {
public:
using MutexType = Mutex;
friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
/**
* Destructor releases.
*/
~LockedPtrBase() {
if (parent_) {
LockPolicy::unlock(parent_->mutex_);
}
}
/**
* 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) {
LockPolicy::lock(parent_->mutex_);
}
template <class Rep, class Period>
LockedPtrBase(
SynchronizedType* parent,
const std::chrono::duration<Rep, Period>& timeout) {
if (LockPolicy::try_lock_for(parent->mutex_, timeout)) {
this->parent_ = parent;
}
}
LockedPtrBase(LockedPtrBase&& rhs) noexcept : parent_(rhs.parent_) {
rhs.parent_ = nullptr;
}
LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
if (parent_) {
LockPolicy::unlock(parent_->mutex_);
}
parent_ = rhs.parent_;
rhs.parent_ = nullptr;
return *this;
}
using UnlockerData = SynchronizedType*;
/**
* Get a pointer to the Synchronized object from the UnlockerData.
*
* In the generic case UnlockerData is just the Synchronized pointer,
* so we return it as is. (This function is more interesting in the
* std::mutex specialization below.)
*/
static SynchronizedType* getSynchronized(UnlockerData data) {
return data;
}
UnlockerData releaseLock() {
DCHECK(parent_ != nullptr);
auto current = parent_;
parent_ = nullptr;
LockPolicy::unlock(current->mutex_);
return current;
}
void reacquireLock(UnlockerData&& data) {
DCHECK(parent_ == nullptr);
parent_ = data;
LockPolicy::lock(parent_->mutex_);
}
SynchronizedType* parent_ = nullptr;
};
/**
* LockedPtrBase specialization for use with std::mutex.
*
* When std::mutex is used we use a std::unique_lock to hold the mutex.
* This makes it possible to use std::condition_variable with a
* Synchronized<T, std::mutex>.
*/
template <class SynchronizedType, class LockPolicy>
class LockedPtrBase<SynchronizedType, std::mutex, LockPolicy> {
public:
using MutexType = std::mutex;
friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
/**
* Destructor releases.
*/
~LockedPtrBase() {
// The std::unique_lock will automatically release the lock when it is
// destroyed, so we don't need to do anything extra here.
}
LockedPtrBase(LockedPtrBase&& rhs) noexcept
: lock_(std::move(rhs.lock_)), parent_(rhs.parent_) {
rhs.parent_ = nullptr;
}
LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
lock_ = std::move(rhs.lock_);
parent_ = rhs.parent_;
rhs.parent_ = nullptr;
return *this;
}
/**
* Get a reference to the std::unique_lock.
*
* This is provided so that callers can use Synchronized<T, std::mutex>
* with a std::condition_variable.
*
* While this API could be used to bypass the normal Synchronized APIs and
* manually interact with the underlying unique_lock, this is strongly
* discouraged.
*/
std::unique_lock<std::mutex>& getUniqueLock() {
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)
: lock_(parent->mutex_), parent_(parent) {}
using UnlockerData =
std::pair<std::unique_lock<std::mutex>, SynchronizedType*>;
static SynchronizedType* getSynchronized(const UnlockerData& data) {
return data.second;
}
UnlockerData releaseLock() {
DCHECK(parent_ != nullptr);
UnlockerData data(std::move(lock_), parent_);
parent_ = nullptr;
data.first.unlock();
return data;
}
void reacquireLock(UnlockerData&& data) {
lock_ = std::move(data.first);
lock_.lock();
parent_ = data.second;
}
// The specialization for std::mutex does have to store slightly more
// state than the default implementation.
std::unique_lock<std::mutex> lock_;
SynchronizedType* parent_ = nullptr;
};
/**
* This class temporarily unlocks a LockedPtr in a scoped manner.
*/
template <class SynchronizedType, class LockPolicy>
class ScopedUnlocker {
public:
explicit ScopedUnlocker(LockedPtr<SynchronizedType, LockPolicy>* p)
: ptr_(p), data_(ptr_->releaseLock()) {}
ScopedUnlocker(const ScopedUnlocker&) = delete;
ScopedUnlocker& operator=(const ScopedUnlocker&) = delete;
ScopedUnlocker(ScopedUnlocker&& other) noexcept
: ptr_(other.ptr_), data_(std::move(other.data_)) {
other.ptr_ = nullptr;
}
ScopedUnlocker& operator=(ScopedUnlocker&& other) = delete;
~ScopedUnlocker() {
if (ptr_) {
ptr_->reacquireLock(std::move(data_));
}
}
/**
* Return a pointer to the Synchronized object used by this ScopedUnlocker.
*/
SynchronizedType* getSynchronized() const {
return LockedPtr<SynchronizedType, LockPolicy>::getSynchronized(data_);
}
private:
using Data = typename LockedPtr<SynchronizedType, LockPolicy>::UnlockerData;
LockedPtr<SynchronizedType, LockPolicy>* ptr_{nullptr};
Data data_;
};
/**
* A LockedPtr keeps a Synchronized<T> object locked for the duration of
* LockedPtr's existence.
*
* It provides access the datum's members directly by using operator->() and
* operator*().
*
* The LockPolicy parameter controls whether or not the lock is acquired in
* exclusive or shared mode.
*/
template <class SynchronizedType, class LockPolicy>
class LockedPtr : public LockedPtrBase<
SynchronizedType,
typename SynchronizedType::MutexType,
LockPolicy> {
private:
using Base = LockedPtrBase<
SynchronizedType,
typename SynchronizedType::MutexType,
LockPolicy>;
using UnlockerData = typename Base::UnlockerData;
// 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;
friend class ScopedUnlocker<SynchronizedType, LockPolicy>;
/**
* Creates an uninitialized LockedPtr.
*
* Dereferencing an uninitialized LockedPtr is not allowed.
*/
LockedPtr() {}
/**
* Takes a Synchronized<T> and locks it.
*/
explicit LockedPtr(SynchronizedType* parent) : Base(parent) {}
/**
* Takes a Synchronized<T> and attempts to lock it, within the specified
* timeout.
*
* Blocks until the lock is acquired or until the specified timeout expires.
* If the timeout expired without acquiring the lock, the LockedPtr will be
* null, and LockedPtr::isNull() will return true.
*/
template <class Rep, class Period>
LockedPtr(
SynchronizedType* parent,
const std::chrono::duration<Rep, Period>& timeout)
: Base(parent, timeout) {}
/**
* Move constructor.
*/
LockedPtr(LockedPtr&& rhs) noexcept = default;
/**
* Move assignment operator.
*/
LockedPtr& operator=(LockedPtr&& rhs) noexcept = default;
/*
* Copy constructor and assignment operator are deleted.
*/
LockedPtr(const LockedPtr& rhs) = delete;
LockedPtr& operator=(const LockedPtr& rhs) = delete;
/**
* Destructor releases.
*/
~LockedPtr() {}
/**
* Check if this LockedPtr is uninitialized, or points to valid locked data.
*
* This method can be used to check if a timed-acquire operation succeeded.
* If an acquire operation times out it will result in a null LockedPtr.
*
* A LockedPtr is always either null, or holds a lock to valid data.
* Methods such as scopedUnlock() reset the LockedPtr to null for the
* duration of the unlock.
*/
bool isNull() const {
return this->parent_ == nullptr;
}
/**
* Explicit boolean conversion.
*
* Returns !isNull()
*/
explicit operator bool() const {
return this->parent_ != nullptr;
}
/**
* Access the locked data.
*
* This method should only be used if the LockedPtr is valid.
*/
CDataType* operator->() const {
return &this->parent_->datum_;
}
/**
* Access the locked data.
*
* This method should only be used if the LockedPtr is valid.
*/
CDataType& operator*() const {
return this->parent_->datum_;
}
/**
* Temporarily unlock the LockedPtr, and reset it to null.
*
* Returns an helper object that will re-lock and restore the LockedPtr when
* the helper is destroyed. The LockedPtr may not be dereferenced for as
* long as this helper object exists.
*/
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);
}
};
/**
* 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, 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
* manner.
*
* The locks are acquired in order from lowest address to highest address.
* (Note that this is not necessarily the same algorithm used by std::lock().)
*
* For parameters that are const and support shared locks, a read lock is
* acquired. Otherwise an exclusive lock is acquired.
*
* TODO: Extend acquireLocked() with variadic template versions that
* allow for more than 2 Synchronized arguments. (I haven't given too much
* thought about how to implement this. It seems like it would be rather
* complicated, but I think it should be possible.)
*/
template <class Sync1, class Sync2>
std::tuple<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
acquireLocked(Sync1& l1, Sync2& l2) {
if (static_cast<const void*>(&l1) < static_cast<const void*>(&l2)) {
auto p1 = l1.contextualLock();
auto p2 = l2.contextualLock();
return std::make_tuple(std::move(p1), std::move(p2));
} else {
auto p2 = l2.contextualLock();
auto p1 = l1.contextualLock();
return std::make_tuple(std::move(p1), std::move(p2));
}
}
/**
* A version of acquireLocked() that returns a std::pair rather than a
* std::tuple, which is easier to use in many places.
*/
template <class Sync1, class Sync2>
std::pair<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
acquireLockedPair(Sync1& l1, Sync2& l2) {
auto lockedPtrs = acquireLocked(l1, l2);
return {std::move(std::get<0>(lockedPtrs)),
std::move(std::get<1>(lockedPtrs))};
}
/************************************************************************
* NOTE: All APIs below this line will be deprecated in upcoming diffs.
************************************************************************/
// Non-member swap primitive
template <class T, class M>
void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
lhs.swap(rhs);
}
/**
* Disambiguate the name var by concatenating the line number of the original
* point of expansion. This avoids shadowing warnings for nested
* SYNCHRONIZEDs. The name is consistent if used multiple times within
* another macro.
* Only for internal use.
*/
#define SYNCHRONIZED_VAR(var) FB_CONCATENATE(SYNCHRONIZED_##var##_, __LINE__)
/**
* SYNCHRONIZED is the main facility that makes Synchronized<T>
* helpful. It is a pseudo-statement that introduces a scope where the
* object is locked. Inside that scope you get to access the unadorned
* datum.
*
* Example:
*
* Synchronized<vector<int>> svector;
* ...
* SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
* or
* SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
*
* Refer to folly/docs/Synchronized.md for a detailed explanation and more
* examples.
*/
#define SYNCHRONIZED(...) \
FOLLY_PUSH_WARNING \
FOLLY_GCC_DISABLE_WARNING("-Wshadow") \
FOLLY_MSVC_DISABLE_WARNING(4189) /* initialized but unreferenced */ \
FOLLY_MSVC_DISABLE_WARNING(4456) /* declaration hides local */ \
FOLLY_MSVC_DISABLE_WARNING(4457) /* declaration hides parameter */ \
FOLLY_MSVC_DISABLE_WARNING(4458) /* declaration hides member */ \
FOLLY_MSVC_DISABLE_WARNING(4459) /* declaration hides global */ \
FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS \
if (bool SYNCHRONIZED_VAR(state) = false) { \
} else \
for (auto SYNCHRONIZED_VAR(lockedPtr) = \
(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \
!SYNCHRONIZED_VAR(state); \
SYNCHRONIZED_VAR(state) = true) \
for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
*SYNCHRONIZED_VAR(lockedPtr).operator->(); \
!SYNCHRONIZED_VAR(state); \
SYNCHRONIZED_VAR(state) = true) \
FOLLY_POP_WARNING
#define TIMED_SYNCHRONIZED(timeout, ...) \
if (bool SYNCHRONIZED_VAR(state) = false) { \
} else \
for (auto SYNCHRONIZED_VAR(lockedPtr) = \
(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \
!SYNCHRONIZED_VAR(state); \
SYNCHRONIZED_VAR(state) = true) \
for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
(!SYNCHRONIZED_VAR(lockedPtr) \
? nullptr \
: SYNCHRONIZED_VAR(lockedPtr).operator->()); \
!SYNCHRONIZED_VAR(state); \
SYNCHRONIZED_VAR(state) = true)
/**
* Similar to SYNCHRONIZED, but only uses a read lock.
*/
#define SYNCHRONIZED_CONST(...) \
SYNCHRONIZED( \
FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
/**
* Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
*/
#define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
TIMED_SYNCHRONIZED( \
timeout, \
FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
/**
* Synchronizes two Synchronized objects (they may encapsulate
* different data). Synchronization is done in increasing address of
* object order, so there is no deadlock risk.
*/
#define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
if (bool SYNCHRONIZED_VAR(state) = false) { \
} else \
for (auto SYNCHRONIZED_VAR(ptrs) = acquireLockedPair(e1, e2); \
!SYNCHRONIZED_VAR(state); \
SYNCHRONIZED_VAR(state) = true) \
for (auto& n1 = *SYNCHRONIZED_VAR(ptrs).first; !SYNCHRONIZED_VAR(state); \
SYNCHRONIZED_VAR(state) = true) \
for (auto& n2 = *SYNCHRONIZED_VAR(ptrs).second; \
!SYNCHRONIZED_VAR(state); \
SYNCHRONIZED_VAR(state) = true)
} /* namespace folly */