X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2FSynchronized.h;h=5d912034b3342db94f8c1b411a860ca173d08989;hb=58a70372d16f13a952ecc046fa90ce055fb6f36a;hp=f36bf5599c8606a868e47de7a980fce13d44482a;hpb=50f4f92c284492f4bb87f3735c82e180a0e49327;p=folly.git diff --git a/folly/Synchronized.h b/folly/Synchronized.h index f36bf559..5d912034 100644 --- a/folly/Synchronized.h +++ b/folly/Synchronized.h @@ -1,5 +1,5 @@ /* - * Copyright 2016 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. @@ -25,6 +25,7 @@ #pragma once +#include #include #include #include @@ -39,6 +40,25 @@ 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. @@ -46,7 +66,7 @@ class LockedPtr; * It provides wlock() and rlock() methods for shared mutex types, * or lock() methods for purely exclusive mutex types. */ -template +template class SynchronizedBase; /** @@ -56,7 +76,7 @@ class SynchronizedBase; * accessing the data. */ template -class SynchronizedBase { +class SynchronizedBase { public: using LockedPtr = ::folly::LockedPtr; using ConstWLockedPtr = @@ -112,6 +132,171 @@ class SynchronizedBase { 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()); + } }; /** @@ -121,7 +306,7 @@ class SynchronizedBase { * data. */ template -class SynchronizedBase { +class SynchronizedBase { public: using LockedPtr = ::folly::LockedPtr; using ConstLockedPtr = @@ -160,6 +345,57 @@ class SynchronizedBase { 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()); + } }; /** @@ -188,15 +424,18 @@ class SynchronizedBase { template struct Synchronized : public SynchronizedBase< Synchronized, - LockTraits::is_shared> { + MutexLevelValue::value> { private: using Base = - SynchronizedBase, LockTraits::is_shared>; + 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; @@ -213,15 +452,28 @@ struct Synchronized : public SynchronizedBase< * 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) + 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(Synchronized&& rhs) /* may throw */ : Synchronized(std::move(rhs), rhs.contextualLock()) {} /** @@ -250,7 +502,10 @@ struct Synchronized : public SynchronizedBase< * 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) { @@ -457,6 +712,8 @@ struct Synchronized : public SynchronizedBase< friend class folly::LockedPtrBase; template friend class folly::LockedPtr; + template + friend class folly::LockedGuardPtr; /** * Helper constructors to enable Synchronized for @@ -481,6 +738,27 @@ struct Synchronized : public SynchronizedBase< 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. * @@ -514,6 +792,20 @@ class LockedPtrBase { } } + /** + * 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) { @@ -554,6 +846,7 @@ class LockedPtrBase { } UnlockerData releaseLock() { + DCHECK(parent_ != nullptr); auto current = parent_; parent_ = nullptr; LockPolicy::unlock(current->mutex_); @@ -614,6 +907,20 @@ class LockedPtrBase { 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) @@ -627,6 +934,7 @@ class LockedPtrBase { } UnlockerData releaseLock() { + DCHECK(parent_ != nullptr); UnlockerData data(std::move(lock_), parent_); parent_ = nullptr; data.first.unlock(); @@ -701,10 +1009,7 @@ class LockedPtr : public LockedPtrBase< LockPolicy>; using UnlockerData = typename Base::UnlockerData; // CDataType is the DataType with the appropriate const-qualification - using CDataType = typename std::conditional< - std::is_const::value, - typename SynchronizedType::DataType const, - typename SynchronizedType::DataType>::type; + using CDataType = detail::SynchronizedDataType; public: using DataType = typename SynchronizedType::DataType; @@ -810,19 +1115,131 @@ class LockedPtr : public LockedPtrBase< 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); + } }; -namespace detail { -/* - * A helper alias to select a ConstLockedPtr if the template parameter is a - * const Synchronized, or a LockedPtr if the parameter is not const. +/** + * LockedGuardPtr is a simplified version of LockedPtr. + * + * It is non-movable, and supports fewer features than LockedPtr. However, it + * is ever-so-slightly more performant than LockedPtr. (The destructor can + * unconditionally release the lock, without requiring a conditional branch.) + * + * The relationship between LockedGuardPtr and LockedPtr is similar to that + * between std::lock_guard and std::unique_lock. */ -template -using LockedPtrType = typename std::conditional< - std::is_const::value, - typename SynchronizedType::ConstLockedPtr, - typename SynchronizedType::LockedPtr>::type; -} // detail +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 @@ -875,6 +1292,15 @@ 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 @@ -894,32 +1320,38 @@ void swap(Synchronized& lhs, Synchronized& rhs) { */ #define SYNCHRONIZED(...) \ FOLLY_PUSH_WARNING \ - FOLLY_GCC_DISABLE_WARNING(shadow) \ - if (bool SYNCHRONIZED_state = false) { \ + 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_lockedPtr = \ + for (auto SYNCHRONIZED_VAR(lockedPtr) = \ (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \ - !SYNCHRONIZED_state; \ - SYNCHRONIZED_state = true) \ + !SYNCHRONIZED_VAR(state); \ + SYNCHRONIZED_VAR(state) = true) \ for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \ - *SYNCHRONIZED_lockedPtr.operator->(); \ - !SYNCHRONIZED_state; \ - SYNCHRONIZED_state = true) \ + *SYNCHRONIZED_VAR(lockedPtr).operator->(); \ + !SYNCHRONIZED_VAR(state); \ + SYNCHRONIZED_VAR(state) = true) \ FOLLY_POP_WARNING #define TIMED_SYNCHRONIZED(timeout, ...) \ - if (bool SYNCHRONIZED_state = false) { \ + if (bool SYNCHRONIZED_VAR(state) = false) { \ } else \ - for (auto SYNCHRONIZED_lockedPtr = \ + for (auto SYNCHRONIZED_VAR(lockedPtr) = \ (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \ - !SYNCHRONIZED_state; \ - SYNCHRONIZED_state = true) \ + !SYNCHRONIZED_VAR(state); \ + SYNCHRONIZED_VAR(state) = true) \ for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \ - (!SYNCHRONIZED_lockedPtr \ + (!SYNCHRONIZED_VAR(lockedPtr) \ ? nullptr \ - : SYNCHRONIZED_lockedPtr.operator->()); \ - !SYNCHRONIZED_state; \ - SYNCHRONIZED_state = true) + : SYNCHRONIZED_VAR(lockedPtr).operator->()); \ + !SYNCHRONIZED_VAR(state); \ + SYNCHRONIZED_VAR(state) = true) /** * Similar to SYNCHRONIZED, but only uses a read lock. @@ -938,39 +1370,21 @@ void swap(Synchronized& lhs, Synchronized& rhs) { 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) +#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 */