X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=blobdiff_plain;f=folly%2FSynchronized.h;h=238b2d8ea2c02522c79176e17f19f898113c8558;hp=8a2f9b95c731943a44453afa12bc3b707976edbe;hb=fd0b3090840d742492793fe306185550f0300283;hpb=7c4e381ec6b27e098b1c69f659e10c9ac09c4126 diff --git a/folly/Synchronized.h b/folly/Synchronized.h index 8a2f9b95..238b2d8e 100644 --- a/folly/Synchronized.h +++ b/folly/Synchronized.h @@ -1,5 +1,5 @@ /* - * Copyright 2015 Facebook, Inc. + * 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. @@ -18,187 +18,385 @@ * This module implements a Synchronized abstraction useful in * mutex-based concurrency. * - * @author: Andrei Alexandrescu (andrei.alexandrescu@fb.com) + * 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. */ -#ifndef SYNCHRONIZED_H_ -#define SYNCHRONIZED_H_ +#pragma once -#include -#include -#include +#include +#include #include +#include #include +#include +#include +#include namespace folly { -namespace detail { -enum InternalDoNotUse {}; +template +class LockedPtrBase; +template +class LockedPtr; +template +class LockedGuardPtr; /** - * Free function adaptors for std:: and boost:: + * 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. */ - -// Android, OSX, and Cygwin don't have timed mutexes -#if defined(ANDROID) || defined(__ANDROID__) || \ - defined(__APPLE__) || defined(__CYGWIN__) -# define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES 0 -#else -# define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES 1 -#endif +template +using MutexLevelValue = detail::MutexLevelValueImpl< + true, + LockTraits::is_shared, + LockTraits::is_upgrade>; /** - * Yields true iff T has .lock() and .unlock() member functions. This - * is done by simply enumerating the mutexes with this interface in - * std and boost. + * 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 -struct HasLockUnlock { - enum { value = IsOneOf::value }; -}; +template +class SynchronizedBase; /** - * Yields true iff T has .lock_shared() and .unlock_shared() member functions. - * This is done by simply enumerating the mutexes with this interface. + * SynchronizedBase specialization for shared mutex types. + * + * This class provides wlock() and rlock() methods for acquiring the lock and + * accessing the data. */ -template -struct HasLockSharedUnlockShared { - enum { value = IsOneOf::value }; +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()); + } }; /** - * Acquires a mutex for reading by calling .lock(). + * SynchronizedBase specialization for upgrade mutex types. * - * This variant is not appropriate for shared mutexes. + * 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 -typename std::enable_if< - HasLockUnlock::value && !HasLockSharedUnlockShared::value>::type -acquireRead(T& mutex) { - mutex.lock(); -} +template +class SynchronizedBase + : public SynchronizedBase { + public: + using UpgradeLockedPtr = ::folly::LockedPtr; + using ConstUpgradeLockedPtr = + ::folly::LockedPtr; + using UpgradeLockedGuardPtr = + ::folly::LockedGuardPtr; + using ConstUpgradeLockedGuardPtr = + ::folly::LockedGuardPtr; -/** - * Acquires a mutex for reading by calling .lock_shared(). - * - * This variant is not appropriate for nonshared mutexes. - */ -template -typename std::enable_if::value>::type -acquireRead(T& mutex) { - mutex.lock_shared(); -} + /** + * 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)); + } -/** - * Acquires a mutex for reading and writing by calling .lock(). - */ -template -typename std::enable_if::value>::type -acquireReadWrite(T& mutex) { - mutex.lock(); -} + /** + * 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); + } -#if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES -/** - * Acquires a mutex for reading by calling .try_lock_shared_for(). This applies - * to boost::shared_mutex. - */ -template -typename std::enable_if< - IsOneOf::value, bool>::type -acquireRead(T& mutex, - unsigned int milliseconds) { - return mutex.try_lock_shared_for(boost::chrono::milliseconds(milliseconds)); -} + /** + * 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); + } -/** - * Acquires a mutex for reading and writing with timeout by calling - * .try_lock_for(). This applies to two of the std mutex classes as - * enumerated below. - */ -template -typename std::enable_if< - IsOneOf::value, bool>::type -acquireReadWrite(T& mutex, - unsigned int milliseconds) { - // work around try_lock_for bug in some gcc versions, see - // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54562 - // TODO: Fixed in gcc-4.9.0. - return mutex.try_lock() - || (milliseconds > 0 && - mutex.try_lock_until(std::chrono::system_clock::now() + - std::chrono::milliseconds(milliseconds))); -} + /** + * 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()); + } +}; /** - * Acquires a mutex for reading and writing with timeout by calling - * .try_lock_for(). This applies to three of the boost mutex classes as - * enumerated below. + * SynchronizedBase specialization for non-shared mutex types. + * + * This class provides lock() methods for acquiring the lock and accessing the + * data. */ -template -typename std::enable_if< - IsOneOf::value, bool>::type -acquireReadWrite(T& mutex, - unsigned int milliseconds) { - return mutex.try_lock_for(boost::chrono::milliseconds(milliseconds)); -} -#endif // FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES +template +class SynchronizedBase { + public: + using LockedPtr = ::folly::LockedPtr; + using ConstLockedPtr = + ::folly::LockedPtr; -/** - * Releases a mutex previously acquired for reading by calling - * .unlock(). The exception is boost::shared_mutex, which has a - * special primitive called .unlock_shared(). - */ -template -typename std::enable_if< - HasLockUnlock::value && !HasLockSharedUnlockShared::value>::type -releaseRead(T& mutex) { - mutex.unlock(); -} + /** + * Acquire a lock, and return a LockedPtr that can be used to safely access + * the datum. + */ + LockedPtr lock() { + return LockedPtr(static_cast(this)); + } -/** - * Special case for boost::shared_mutex. - */ -template -typename std::enable_if::value>::type -releaseRead(T& mutex) { - mutex.unlock_shared(); -} + /** + * Acquire a lock, and return a ConstLockedPtr that can be used to safely + * access the datum. + */ + ConstLockedPtr lock() const { + return ConstLockedPtr(static_cast(this)); + } -/** - * Releases a mutex previously acquired for reading-writing by calling - * .unlock(). - */ -template -typename std::enable_if::value>::type -releaseReadWrite(T& mutex) { - mutex.unlock(); -} + /** + * 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); + } -} // namespace detail + /** + * 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 @@ -209,65 +407,74 @@ releaseReadWrite(T& mutex) { * reviewer. In contrast, the code that uses Synchronized correctly * looks simple and intuitive. * - * The second parameter must be a mutex type. Supported mutexes are - * std::mutex, std::recursive_mutex, std::timed_mutex, - * std::recursive_timed_mutex, boost::mutex, boost::recursive_mutex, - * boost::shared_mutex, boost::timed_mutex, - * boost::recursive_timed_mutex, and the folly/RWSpinLock.h - * classes. + * 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. * - * You may define Synchronized support by defining 4-6 primitives in - * the same namespace as the mutex class (found via ADL). The - * primitives are: acquireRead, acquireReadWrite, releaseRead, and - * releaseReadWrite. Two optional primitives for timout operations are - * overloads of acquireRead and acquireReadWrite. For signatures, - * refer to the namespace detail below, which implements the - * primitives for mutexes in std and boost. + * 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 { - /** - * Default constructor leaves both members call their own default - * constructor. - */ - Synchronized() = default; - +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}; + // 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; + /** - * 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 + * Default constructor leaves both members call their own default + * constructor. */ - template - Synchronized(const Synchronized& rhs, - const Guard& /*guard*/) noexcept(nxCopyCtor) - : datum_(rhs.datum_) {} - - template - Synchronized(Synchronized&& rhs, const Guard& /*guard*/) noexcept(nxMoveCtor) - : datum_(std::move(rhs.datum_)) {} + Synchronized() = default; - public: /** * 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(rhs, rhs.operator->()) {} + public: + /* implicit */ Synchronized(typename std::conditional< + std::is_copy_constructible::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) noexcept(nxMoveCtor) - : Synchronized(std::move(rhs), rhs.operator->()) {} + Synchronized(Synchronized&& rhs) /* may throw */ + : Synchronized(std::move(rhs), rhs.contextualLock()) {} /** * Constructor taking a datum as argument copies it. There is no @@ -284,10 +491,10 @@ struct Synchronized { /** * Lets you construct non-movable types in-place. Use the constexpr - * instance `construct_in_place` as the first argument. + * instance `in_place` as the first argument. */ template - explicit Synchronized(construct_in_place_t, Args&&... args) + explicit Synchronized(in_place_t, Args&&... args) : datum_(std::forward(args)...) {} /** @@ -295,7 +502,10 @@ struct Synchronized { * mutex. It locks the two objects in ascending order of their * addresses. */ - Synchronized& operator=(const Synchronized& rhs) { + Synchronized& operator=(typename std::conditional< + std::is_copy_assignable::value, + const Synchronized&, + NonImplementedType>::type rhs) { if (this == &rhs) { // Self-assignment, pass. } else if (this < &rhs) { @@ -349,219 +559,66 @@ struct Synchronized { } /** - * A LockedPtr lp keeps a modifiable (i.e. non-const) - * Synchronized object locked for the duration of lp's - * existence. Because of this, you get to access the datum's methods - * directly by using lp->fun(). + * 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. */ - struct LockedPtr { - /** - * Found no reason to leave this hanging. - */ - LockedPtr() = delete; - - /** - * Takes a Synchronized and locks it. - */ - explicit LockedPtr(Synchronized* parent) : parent_(parent) { - acquire(); - } - - /** - * Takes a Synchronized and attempts to lock it for some - * milliseconds. If not, the LockedPtr will be subsequently null. - */ - LockedPtr(Synchronized* parent, unsigned int milliseconds) { - using namespace detail; - if (acquireReadWrite(parent->mutex_, milliseconds)) { - parent_ = parent; - return; - } - // Could not acquire the resource, pointer is null - parent_ = nullptr; - } - - /** - * This is used ONLY inside SYNCHRONIZED_DUAL. It initializes - * everything properly, but does not lock the parent because it - * "knows" someone else will lock it. Please do not use. - */ - LockedPtr(Synchronized* parent, detail::InternalDoNotUse) - : parent_(parent) { - } - - /** - * Copy ctor adds one lock. - */ - LockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) { - acquire(); - } - - /** - * Assigning from another LockedPtr results in freeing the former - * lock and acquiring the new one. The method works with - * self-assignment (does nothing). - */ - LockedPtr& operator=(const LockedPtr& rhs) { - if (parent_ != rhs.parent_) { - if (parent_) parent_->mutex_.unlock(); - parent_ = rhs.parent_; - acquire(); - } - return *this; - } - - /** - * Destructor releases. - */ - ~LockedPtr() { - using namespace detail; - if (parent_) releaseReadWrite(parent_->mutex_); - } - - /** - * Safe to access the data. Don't save the obtained pointer by - * invoking lp.operator->() by hand. Also, if the method returns a - * handle stored inside the datum, don't use this idiom - use - * SYNCHRONIZED below. - */ - T* operator->() { - return parent_ ? &parent_->datum_ : nullptr; - } - - /** - * This class temporarily unlocks a LockedPtr in a scoped - * manner. It is used inside of the UNSYNCHRONIZED macro. - */ - struct Unsynchronizer { - explicit Unsynchronizer(LockedPtr* p) : parent_(p) { - using namespace detail; - releaseReadWrite(parent_->parent_->mutex_); - } - Unsynchronizer(const Unsynchronizer&) = delete; - Unsynchronizer& operator=(const Unsynchronizer&) = delete; - ~Unsynchronizer() { - parent_->acquire(); - } - LockedPtr* operator->() const { - return parent_; - } - private: - LockedPtr* parent_; - }; - friend struct Unsynchronizer; - Unsynchronizer typeHackDoNotUse(); - - template - friend void lockInOrder(P1& p1, P2& p2); - - private: - void acquire() { - using namespace detail; - if (parent_) acquireReadWrite(parent_->mutex_); - } - - // This is the entire state of LockedPtr. - Synchronized* parent_; - }; - + 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); + } /** - * ConstLockedPtr does exactly what LockedPtr does, but for const - * Synchronized objects. Of interest is that ConstLockedPtr only - * uses a read lock, which is faster but more restrictive - you only - * get to call const methods of the datum. + * contextualRLock() acquires a read lock if the mutex type is shared, + * or a regular exclusive lock for non-shared mutex types. * - * Much of the code between LockedPtr and - * ConstLockedPtr is identical and could be factor out, but there - * are enough nagging little differences to not justify the trouble. + * contextualRLock() when you know that you prefer a read lock (if + * available), even if the Synchronized object itself is non-const. */ - struct ConstLockedPtr { - ConstLockedPtr() = delete; - explicit ConstLockedPtr(const Synchronized* parent) : parent_(parent) { - acquire(); - } - ConstLockedPtr(const Synchronized* parent, detail::InternalDoNotUse) - : parent_(parent) { - } - ConstLockedPtr(const ConstLockedPtr& rhs) : parent_(rhs.parent_) { - acquire(); - } - explicit ConstLockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) { - acquire(); - } - ConstLockedPtr(const Synchronized* parent, unsigned int milliseconds) { - using namespace detail; - if (acquireRead( - parent->mutex_, - milliseconds)) { - parent_ = parent; - return; - } - // Could not acquire the resource, pointer is null - parent_ = nullptr; - } - - ConstLockedPtr& operator=(const ConstLockedPtr& rhs) { - if (parent_ != rhs.parent_) { - if (parent_) parent_->mutex_.unlock_shared(); - parent_ = rhs.parent_; - acquire(); - } - } - ~ConstLockedPtr() { - using namespace detail; - if (parent_) releaseRead(parent_->mutex_); - } - - const T* operator->() const { - return parent_ ? &parent_->datum_ : nullptr; - } - - struct Unsynchronizer { - explicit Unsynchronizer(ConstLockedPtr* p) : parent_(p) { - using namespace detail; - releaseRead(parent_->parent_->mutex_); - } - Unsynchronizer(const Unsynchronizer&) = delete; - Unsynchronizer& operator=(const Unsynchronizer&) = delete; - ~Unsynchronizer() { - using namespace detail; - acquireRead(parent_->parent_->mutex_); - } - ConstLockedPtr* operator->() const { - return parent_; - } - private: - ConstLockedPtr* parent_; - }; - friend struct Unsynchronizer; - Unsynchronizer typeHackDoNotUse(); - - template - friend void lockInOrder(P1& p1, P2& p2); - - private: - void acquire() { - using namespace detail; - if (parent_) acquireRead(parent_->mutex_); - } - - const Synchronized* parent_; - }; + 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 + * 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); } /** - * Same, for constant objects. You will be able to invoke only const - * methods. + * 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); @@ -569,31 +626,26 @@ struct Synchronized { /** * Attempts to acquire for a given number of milliseconds. If - * acquisition is unsuccessful, the returned LockedPtr is NULL. + * 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, milliseconds); + return LockedPtr(this, std::chrono::milliseconds(milliseconds)); } /** - * As above, for a constant object. + * 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, milliseconds); - } - - /** - * Used by SYNCHRONIZED_DUAL. - */ - LockedPtr internalDoNotUse() { - return LockedPtr(this, detail::InternalDoNotUse()); - } - - /** - * ditto - */ - ConstLockedPtr internalDoNotUse() const { - return ConstLockedPtr(this, detail::InternalDoNotUse()); + return ConstLockedPtr(this, std::chrono::milliseconds(milliseconds)); } /** @@ -601,6 +653,9 @@ struct Synchronized { * 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; @@ -630,7 +685,7 @@ struct Synchronized { * held only briefly. */ void swap(T& rhs) { - LockedPtr guard = operator->(); + LockedPtr guard(this); using std::swap; swap(datum_, rhs); @@ -640,7 +695,7 @@ struct Synchronized { * Copies datum to a given target. */ void copy(T* target) const { - ConstLockedPtr guard = operator->(); + ConstLockedPtr guard(this); *target = datum_; } @@ -648,21 +703,604 @@ struct Synchronized { * Returns a fresh copy of the datum. */ T copy() const { - ConstLockedPtr guard = operator->(); + ConstLockedPtr guard(this); return datum_; } -private: + 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; +} // namespace 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); } +/** + * 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 * helpful. It is a pseudo-statement that introduces a scope where the @@ -680,86 +1318,73 @@ void swap(Synchronized& lhs, Synchronized& rhs) { * Refer to folly/docs/Synchronized.md for a detailed explanation and more * examples. */ -#define SYNCHRONIZED(...) \ - _Pragma("GCC diagnostic push") \ - _Pragma("GCC diagnostic ignored \"-Wshadow\"") \ - if (bool SYNCHRONIZED_state = false) {} else \ - for (auto SYNCHRONIZED_lockedPtr = \ - (FB_ARG_2_OR_1(__VA_ARGS__)).operator->(); \ - !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \ - for (auto& FB_ARG_1(__VA_ARGS__) = \ - *SYNCHRONIZED_lockedPtr.operator->(); \ - !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \ - _Pragma("GCC diagnostic pop") - -#define TIMED_SYNCHRONIZED(timeout, ...) \ - if (bool SYNCHRONIZED_state = false) {} else \ - for (auto SYNCHRONIZED_lockedPtr = \ - (FB_ARG_2_OR_1(__VA_ARGS__)).timedAcquire(timeout); \ - !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \ - for (auto FB_ARG_1(__VA_ARGS__) = \ - SYNCHRONIZED_lockedPtr.operator->(); \ - !SYNCHRONIZED_state; SYNCHRONIZED_state = true) +#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_ARG_1(__VA_ARGS__), \ - (FB_ARG_2_OR_1(__VA_ARGS__)).asConst()) +#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_ARG_1(__VA_ARGS__), \ - (FB_ARG_2_OR_1(__VA_ARGS__)).asConst()) - -/** - * Temporarily disables synchronization inside a SYNCHRONIZED block. - */ -#define UNSYNCHRONIZED(name) \ - for (decltype(SYNCHRONIZED_lockedPtr.typeHackDoNotUse()) \ - SYNCHRONIZED_state3(&SYNCHRONIZED_lockedPtr); \ - !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \ - for (auto name = *SYNCHRONIZED_state3.operator->(); \ - !SYNCHRONIZED_state; SYNCHRONIZED_state = true) - -/** - * Locks two objects in increasing order of their addresses. - */ -template -void lockInOrder(P1& p1, P2& p2) { - if (static_cast(p1.operator->()) > - static_cast(p2.operator->())) { - p2.acquire(); - p1.acquire(); - } else { - p1.acquire(); - p2.acquire(); - } -} +#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_state = false) {} else \ - for (auto SYNCHRONIZED_lp1 = (e1).internalDoNotUse(); \ - !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \ - for (auto& n1 = *SYNCHRONIZED_lp1.operator->(); \ - !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \ - for (auto SYNCHRONIZED_lp2 = (e2).internalDoNotUse(); \ - !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \ - for (auto& n2 = *SYNCHRONIZED_lp2.operator->(); \ - !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \ - if ((::folly::lockInOrder( \ - SYNCHRONIZED_lp1, SYNCHRONIZED_lp2), \ - false)) {} \ - else +#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 */ - -#endif // SYNCHRONIZED_H_