/* * Copyright 2016 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 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 #include #include #include #include #include #include #include namespace folly { template class LockedPtrBase; template class LockedPtr; template 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 using MutexLevelValue = detail::MutexLevelValueImpl< true, LockTraits::is_shared, LockTraits::is_upgrade>; /** * SynchronizedBase is a helper parent class for Synchronized. * * It provides wlock() and rlock() methods for shared mutex types, * or lock() methods for purely exclusive mutex types. */ template 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 SynchronizedBase { public: using LockedPtr = ::folly::LockedPtr; using ConstWLockedPtr = ::folly::LockedPtr; using ConstLockedPtr = ::folly::LockedPtr; /** * 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(this)); } ConstWLockedPtr wlock() const { return ConstWLockedPtr(static_cast(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(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 LockedPtr wlock(const std::chrono::duration& timeout) { return LockedPtr(static_cast(this), timeout); } template ConstWLockedPtr wlock( const std::chrono::duration& timeout) const { return ConstWLockedPtr(static_cast(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 ConstLockedPtr rlock( const std::chrono::duration& timeout) const { return ConstLockedPtr(static_cast(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 auto withWLock(Function&& function) { LockedGuardPtr guardPtr( static_cast(this)); return function(*guardPtr); } template auto withWLock(Function&& function) const { LockedGuardPtr guardPtr( static_cast(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 auto withWLockPtr(Function&& function) { return function(wlock()); } template 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 auto withRLock(Function&& function) const { LockedGuardPtr guardPtr( static_cast(this)); return function(*guardPtr); } template 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 SynchronizedBase : public SynchronizedBase { public: using UpgradeLockedPtr = ::folly::LockedPtr; using ConstUpgradeLockedPtr = ::folly::LockedPtr; using UpgradeLockedGuardPtr = ::folly::LockedGuardPtr; using ConstUpgradeLockedGuardPtr = ::folly::LockedGuardPtr; /** * 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(this)); } ConstUpgradeLockedPtr ulock() const { return ConstUpgradeLockedPtr(static_cast(this)); } /** * Acquire an upgrade lock and return a LockedPtr that can be used to safely * access the datum * * And the const version */ template UpgradeLockedPtr ulock(const std::chrono::duration& timeout) { return UpgradeLockedPtr(static_cast(this), timeout); } template UpgradeLockedPtr ulock( const std::chrono::duration& timeout) const { return ConstUpgradeLockedPtr(static_cast(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 auto withULock(Function&& function) const { ConstUpgradeLockedGuardPtr guardPtr(static_cast(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 auto withULockPtr(Function&& function) { return function(ulock()); } template 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 SynchronizedBase { public: using LockedPtr = ::folly::LockedPtr; using ConstLockedPtr = ::folly::LockedPtr; /** * Acquire a lock, and return a LockedPtr that can be used to safely access * the datum. */ LockedPtr lock() { return LockedPtr(static_cast(this)); } /** * Acquire a lock, and return a ConstLockedPtr that can be used to safely * access the datum. */ ConstLockedPtr lock() const { return ConstLockedPtr(static_cast(this)); } /** * Attempts to acquire the lock, or fails if the timeout elapses first. * If acquisition is unsuccessful, the returned LockedPtr will be null. */ template LockedPtr lock(const std::chrono::duration& timeout) { return LockedPtr(static_cast(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 ConstLockedPtr lock(const std::chrono::duration& timeout) const { return ConstLockedPtr(static_cast(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 auto withLock(Function&& function) { LockedGuardPtr guardPtr( static_cast(this)); return function(*guardPtr); } template auto withLock(Function&& function) const { LockedGuardPtr guardPtr( static_cast(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 auto withLockPtr(Function&& function) { return function(lock()); } template auto withLockPtr(Function&& function) const { return function(lock()); } }; /** * Synchronized 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 correctly * looks simple and intuitive. * * The second parameter must be a mutex type. Any mutex type supported by * LockTraits 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 struct Synchronized : public SynchronizedBase< Synchronized, MutexLevelValue::value> { private: using Base = SynchronizedBase, MutexLevelValue::value>; static constexpr bool nxCopyCtor{ std::is_nothrow_copy_constructible::value}; static constexpr bool nxMoveCtor{ std::is_nothrow_move_constructible::value}; 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 */ 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) /* 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 `construct_in_place` as the first argument. */ template explicit Synchronized(construct_in_place_t, Args&&... args) : datum_(std::forward(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=(const Synchronized& 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 LockedPtr contextualLock(const std::chrono::duration& timeout) { return LockedPtr(this, timeout); } template ConstLockedPtr contextualLock( const std::chrono::duration& 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 object itself is non-const. */ ConstLockedPtr contextualRLock() const { return ConstLockedPtr(this); } template ConstLockedPtr contextualRLock( const std::chrono::duration& 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 NULL. * * 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 NULL. * * 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 friend class folly::LockedPtrBase; template friend class folly::LockedPtr; template 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 ScopedUnlocker; namespace detail { /* * A helper alias that resolves to "const T" if the template parameter * is a const Synchronized, or "T" if the parameter is not const. */ template using SynchronizedDataType = typename std::conditional< std::is_const::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, or a LockedPtr if the parameter is not const. */ template using LockedPtrType = typename std::conditional< std::is_const::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. * * 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, 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 LockedPtrBase { public: using MutexType = Mutex; friend class folly::ScopedUnlocker; /** * 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 LockedPtrBase( SynchronizedType* parent, const std::chrono::duration& 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. */ template class LockedPtrBase { public: using MutexType = std::mutex; friend class folly::ScopedUnlocker; /** * 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 * 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& 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, 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 lock_; SynchronizedType* parent_ = nullptr; }; /** * This class temporarily unlocks a LockedPtr in a scoped manner. */ template class ScopedUnlocker { public: explicit ScopedUnlocker(LockedPtr* 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::getSynchronized(data_); } private: using Data = typename LockedPtr::UnlockerData; LockedPtr* ptr_{nullptr}; Data data_; }; /** * A LockedPtr keeps a Synchronized 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 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; public: using DataType = typename SynchronizedType::DataType; using MutexType = typename SynchronizedType::MutexType; using Synchronized = typename std::remove_const::type; friend class ScopedUnlocker; /** * Creates an uninitialized LockedPtr. * * Dereferencing an uninitialized LockedPtr is not allowed. */ LockedPtr() {} /** * Takes a Synchronized and locks it. */ explicit LockedPtr(SynchronizedType* parent) : Base(parent) {} /** * Takes a Synchronized 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 LockedPtr( SynchronizedType* parent, const std::chrono::duration& 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 scopedUnlock() { return ScopedUnlocker(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::is_upgrade>::type> LockedPtr moveFromUpgradeToWrite() { auto* parent_to_pass_on = this->parent_; this->parent_ = nullptr; return LockedPtr( 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::is_upgrade>::type> LockedPtr moveFromWriteToUpgrade() { auto* parent_to_pass_on = this->parent_; this->parent_ = nullptr; return LockedPtr( 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::is_upgrade>::type> LockedPtr moveFromUpgradeToRead() { auto* parent_to_pass_on = this->parent_; this->parent_ = nullptr; return LockedPtr( 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::is_upgrade>::type> LockedPtr moveFromWriteToRead() { auto* parent_to_pass_on = this->parent_; this->parent_ = nullptr; return LockedPtr( 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 LockedGuardPtr { private: // CDataType is the DataType with the appropriate const-qualification using CDataType = detail::SynchronizedDataType; public: using DataType = typename SynchronizedType::DataType; using MutexType = typename SynchronizedType::MutexType; using Synchronized = typename std::remove_const::type; LockedGuardPtr() = delete; /** * Takes a Synchronized 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 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 std::tuple, detail::LockedPtrType> acquireLocked(Sync1& l1, Sync2& l2) { if (static_cast(&l1) < static_cast(&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 std::pair, detail::LockedPtrType> 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 void swap(Synchronized& lhs, Synchronized& rhs) { lhs.swap(rhs); } /** * SYNCHRONIZED is the main facility that makes Synchronized * 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> svector; * ... * SYNCHRONIZED (svector) { ... use svector as a vector ... } * or * SYNCHRONIZED (v, svector) { ... use v as a vector ... } * * Refer to folly/docs/Synchronized.md for a detailed explanation and more * examples. */ #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 = \ (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \ !SYNCHRONIZED_state; \ SYNCHRONIZED_state = true) \ for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \ *SYNCHRONIZED_lockedPtr.operator->(); \ !SYNCHRONIZED_state; \ SYNCHRONIZED_state = true) \ FOLLY_POP_WARNING #define TIMED_SYNCHRONIZED(timeout, ...) \ if (bool SYNCHRONIZED_state = false) { \ } else \ for (auto SYNCHRONIZED_lockedPtr = \ (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \ !SYNCHRONIZED_state; \ SYNCHRONIZED_state = true) \ for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \ (!SYNCHRONIZED_lockedPtr \ ? nullptr \ : SYNCHRONIZED_lockedPtr.operator->()); \ !SYNCHRONIZED_state; \ SYNCHRONIZED_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()) /** * Temporarily disables synchronization inside a SYNCHRONIZED block. * * Note: This macro is deprecated, and kind of broken. The input parameter * does not control what it unlocks--it always unlocks the lock acquired by the * most recent SYNCHRONIZED scope. If you have two nested SYNCHRONIZED blocks, * UNSYNCHRONIZED always unlocks the inner-most, even if you pass in the * variable name used in the outer SYNCHRONIZED block. * * This macro will be removed soon in a subsequent diff. */ #define UNSYNCHRONIZED(name) \ for (auto SYNCHRONIZED_state3 = SYNCHRONIZED_lockedPtr.scopedUnlock(); \ !SYNCHRONIZED_state; \ SYNCHRONIZED_state = true) \ for (auto& name = *SYNCHRONIZED_state3.getSynchronized(); \ !SYNCHRONIZED_state; \ SYNCHRONIZED_state = true) /** * 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_state = false) { \ } else \ for (auto SYNCHRONIZED_ptrs = acquireLockedPair(e1, e2); \ !SYNCHRONIZED_state; \ SYNCHRONIZED_state = true) \ for (auto& n1 = *SYNCHRONIZED_ptrs.first; !SYNCHRONIZED_state; \ SYNCHRONIZED_state = true) \ for (auto& n2 = *SYNCHRONIZED_ptrs.second; !SYNCHRONIZED_state; \ SYNCHRONIZED_state = true) } /* namespace folly */