2 * Copyright 2011-present Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * This module implements a Synchronized abstraction useful in
19 * mutex-based concurrency.
21 * The Synchronized<T, Mutex> class is the primary public API exposed by this
22 * module. See folly/docs/Synchronized.md for a more complete explanation of
23 * this class and its benefits.
28 #include <folly/Likely.h>
29 #include <folly/LockTraits.h>
30 #include <folly/Preprocessor.h>
31 #include <folly/SharedMutex.h>
32 #include <folly/Traits.h>
33 #include <glog/logging.h>
35 #include <type_traits>
39 template <class LockedType, class Mutex, class LockPolicy>
41 template <class LockedType, class LockPolicy>
43 template <class LockedType, class LockPolicy = LockPolicyExclusive>
47 * Public version of LockInterfaceDispatcher that contains the MutexLevel enum
48 * for the passed in mutex type
50 * This is decoupled from MutexLevelValueImpl in LockTraits.h because this
51 * ensures that a heterogenous mutex with a different API can be used. For
52 * example - if a mutex does not have a lock_shared() method but the
53 * LockTraits specialization for it supports a static non member
54 * lock_shared(Mutex&) it can be used as a shared mutex and will provide
55 * rlock() and wlock() functions.
57 template <class Mutex>
58 using MutexLevelValue = detail::MutexLevelValueImpl<
60 LockTraits<Mutex>::is_shared,
61 LockTraits<Mutex>::is_upgrade>;
64 * SynchronizedBase is a helper parent class for Synchronized<T>.
66 * It provides wlock() and rlock() methods for shared mutex types,
67 * or lock() methods for purely exclusive mutex types.
69 template <class Subclass, detail::MutexLevel level>
70 class SynchronizedBase;
73 * SynchronizedBase specialization for shared mutex types.
75 * This class provides wlock() and rlock() methods for acquiring the lock and
78 template <class Subclass>
79 class SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
81 using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
82 using ConstWLockedPtr =
83 ::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
84 using ConstLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyShared>;
87 * Acquire an exclusive lock, and return a LockedPtr that can be used to
88 * safely access the datum.
90 * LockedPtr offers operator -> and * to provide access to the datum.
91 * The lock will be released when the LockedPtr is destroyed.
94 return LockedPtr(static_cast<Subclass*>(this));
96 ConstWLockedPtr wlock() const {
97 return ConstWLockedPtr(static_cast<const Subclass*>(this));
101 * Acquire a read lock, and return a ConstLockedPtr that can be used to
102 * safely access the datum.
104 ConstLockedPtr rlock() const {
105 return ConstLockedPtr(static_cast<const Subclass*>(this));
109 * Attempts to acquire the lock, or fails if the timeout elapses first.
110 * If acquisition is unsuccessful, the returned LockedPtr will be null.
112 * (Use LockedPtr::isNull() to check for validity.)
114 template <class Rep, class Period>
115 LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) {
116 return LockedPtr(static_cast<Subclass*>(this), timeout);
118 template <class Rep, class Period>
119 ConstWLockedPtr wlock(
120 const std::chrono::duration<Rep, Period>& timeout) const {
121 return ConstWLockedPtr(static_cast<const Subclass*>(this), timeout);
125 * Attempts to acquire the lock, or fails if the timeout elapses first.
126 * If acquisition is unsuccessful, the returned LockedPtr will be null.
128 * (Use LockedPtr::isNull() to check for validity.)
130 template <class Rep, class Period>
131 ConstLockedPtr rlock(
132 const std::chrono::duration<Rep, Period>& timeout) const {
133 return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
137 * Note: C++ 17 adds guaranteed copy elision. (http://wg21.link/P0135)
138 * Once compilers support this, it would be nice to add wguard() and rguard()
139 * methods that return LockedGuardPtr objects.
143 * Invoke a function while holding the lock exclusively.
145 * A reference to the datum will be passed into the function as its only
148 * This can be used with a lambda argument for easily defining small critical
149 * sections in the code. For example:
151 * auto value = obj.withWLock([](auto& data) {
153 * return data.getValue();
156 template <class Function>
157 auto withWLock(Function&& function) {
158 LockedGuardPtr<Subclass, LockPolicyExclusive> guardPtr(
159 static_cast<Subclass*>(this));
160 return function(*guardPtr);
162 template <class Function>
163 auto withWLock(Function&& function) const {
164 LockedGuardPtr<const Subclass, LockPolicyExclusive> guardPtr(
165 static_cast<const Subclass*>(this));
166 return function(*guardPtr);
170 * Invoke a function while holding the lock exclusively.
172 * This is similar to withWLock(), but the function will be passed a
173 * LockedPtr rather than a reference to the data itself.
175 * This allows scopedUnlock() to be called on the LockedPtr argument if
178 template <class Function>
179 auto withWLockPtr(Function&& function) {
180 return function(wlock());
182 template <class Function>
183 auto withWLockPtr(Function&& function) const {
184 return function(wlock());
188 * Invoke a function while holding an the lock in shared mode.
190 * A const reference to the datum will be passed into the function as its
193 template <class Function>
194 auto withRLock(Function&& function) const {
195 LockedGuardPtr<const Subclass, LockPolicyShared> guardPtr(
196 static_cast<const Subclass*>(this));
197 return function(*guardPtr);
200 template <class Function>
201 auto withRLockPtr(Function&& function) const {
202 return function(rlock());
207 * SynchronizedBase specialization for upgrade mutex types.
209 * This class provides all the functionality provided by the SynchronizedBase
210 * specialization for shared mutexes and a ulock() method that returns an
211 * upgradable lock RAII proxy
213 template <class Subclass>
214 class SynchronizedBase<Subclass, detail::MutexLevel::UPGRADE>
215 : public SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
217 using UpgradeLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyUpgrade>;
218 using ConstUpgradeLockedPtr =
219 ::folly::LockedPtr<const Subclass, LockPolicyUpgrade>;
220 using UpgradeLockedGuardPtr =
221 ::folly::LockedGuardPtr<Subclass, LockPolicyUpgrade>;
222 using ConstUpgradeLockedGuardPtr =
223 ::folly::LockedGuardPtr<const Subclass, LockPolicyUpgrade>;
226 * Acquire an upgrade lock and return a LockedPtr that can be used to safely
229 * And the const version
231 UpgradeLockedPtr ulock() {
232 return UpgradeLockedPtr(static_cast<Subclass*>(this));
234 ConstUpgradeLockedPtr ulock() const {
235 return ConstUpgradeLockedPtr(static_cast<const Subclass*>(this));
239 * Acquire an upgrade lock and return a LockedPtr that can be used to safely
242 * And the const version
244 template <class Rep, class Period>
245 UpgradeLockedPtr ulock(const std::chrono::duration<Rep, Period>& timeout) {
246 return UpgradeLockedPtr(static_cast<Subclass*>(this), timeout);
248 template <class Rep, class Period>
249 UpgradeLockedPtr ulock(
250 const std::chrono::duration<Rep, Period>& timeout) const {
251 return ConstUpgradeLockedPtr(static_cast<const Subclass*>(this), timeout);
255 * Invoke a function while holding the lock.
257 * A reference to the datum will be passed into the function as its only
260 * This can be used with a lambda argument for easily defining small critical
261 * sections in the code. For example:
263 * auto value = obj.withULock([](auto& data) {
265 * return data.getValue();
268 * This is probably not the function you want. If the intent is to read the
269 * data object and determine whether you should upgrade to a write lock then
270 * the withULockPtr() method should be called instead, since it gives access
271 * to the LockedPtr proxy (which can be upgraded via the
272 * moveFromUpgradeToWrite() method)
274 template <class Function>
275 auto withULock(Function&& function) const {
276 ConstUpgradeLockedGuardPtr guardPtr(static_cast<const Subclass*>(this));
277 return function(*guardPtr);
281 * Invoke a function while holding the lock exclusively.
283 * This is similar to withULock(), but the function will be passed a
284 * LockedPtr rather than a reference to the data itself.
286 * This allows scopedUnlock() and getUniqueLock() to be called on the
287 * LockedPtr argument.
289 * This also allows you to upgrade the LockedPtr proxy to a write state so
290 * that changes can be made to the underlying data
292 template <class Function>
293 auto withULockPtr(Function&& function) {
294 return function(ulock());
296 template <class Function>
297 auto withULockPtr(Function&& function) const {
298 return function(ulock());
303 * SynchronizedBase specialization for non-shared mutex types.
305 * This class provides lock() methods for acquiring the lock and accessing the
308 template <class Subclass>
309 class SynchronizedBase<Subclass, detail::MutexLevel::UNIQUE> {
311 using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
312 using ConstLockedPtr =
313 ::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
316 * Acquire a lock, and return a LockedPtr that can be used to safely access
320 return LockedPtr(static_cast<Subclass*>(this));
324 * Acquire a lock, and return a ConstLockedPtr that can be used to safely
327 ConstLockedPtr lock() const {
328 return ConstLockedPtr(static_cast<const Subclass*>(this));
332 * Attempts to acquire the lock, or fails if the timeout elapses first.
333 * If acquisition is unsuccessful, the returned LockedPtr will be null.
335 template <class Rep, class Period>
336 LockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) {
337 return LockedPtr(static_cast<Subclass*>(this), timeout);
341 * Attempts to acquire the lock, or fails if the timeout elapses first.
342 * If acquisition is unsuccessful, the returned LockedPtr will be null.
344 template <class Rep, class Period>
345 ConstLockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) const {
346 return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
350 * Note: C++ 17 adds guaranteed copy elision. (http://wg21.link/P0135)
351 * Once compilers support this, it would be nice to add guard() methods that
352 * return LockedGuardPtr objects.
356 * Invoke a function while holding the lock.
358 * A reference to the datum will be passed into the function as its only
361 * This can be used with a lambda argument for easily defining small critical
362 * sections in the code. For example:
364 * auto value = obj.withLock([](auto& data) {
366 * return data.getValue();
369 template <class Function>
370 auto withLock(Function&& function) {
371 LockedGuardPtr<Subclass, LockPolicyExclusive> guardPtr(
372 static_cast<Subclass*>(this));
373 return function(*guardPtr);
375 template <class Function>
376 auto withLock(Function&& function) const {
377 LockedGuardPtr<const Subclass, LockPolicyExclusive> guardPtr(
378 static_cast<const Subclass*>(this));
379 return function(*guardPtr);
383 * Invoke a function while holding the lock exclusively.
385 * This is similar to withWLock(), but the function will be passed a
386 * LockedPtr rather than a reference to the data itself.
388 * This allows scopedUnlock() and getUniqueLock() to be called on the
389 * LockedPtr argument.
391 template <class Function>
392 auto withLockPtr(Function&& function) {
393 return function(lock());
395 template <class Function>
396 auto withLockPtr(Function&& function) const {
397 return function(lock());
402 * Synchronized<T> encapsulates an object of type T (a "datum") paired
403 * with a mutex. The only way to access the datum is while the mutex
404 * is locked, and Synchronized makes it virtually impossible to do
405 * otherwise. The code that would access the datum in unsafe ways
406 * would look odd and convoluted, thus readily alerting the human
407 * reviewer. In contrast, the code that uses Synchronized<T> correctly
408 * looks simple and intuitive.
410 * The second parameter must be a mutex type. Any mutex type supported by
411 * LockTraits<Mutex> can be used. By default any class with lock() and
412 * unlock() methods will work automatically. LockTraits can be specialized to
413 * teach Synchronized how to use other custom mutex types. See the
414 * documentation in LockTraits.h for additional details.
416 * Supported mutexes that work by default include std::mutex,
417 * std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex,
418 * folly::SharedMutex, folly::RWSpinLock, and folly::SpinLock.
419 * Include LockTraitsBoost.h to get additional LockTraits specializations to
420 * support the following boost mutex types: boost::mutex,
421 * boost::recursive_mutex, boost::shared_mutex, boost::timed_mutex, and
422 * boost::recursive_timed_mutex.
424 template <class T, class Mutex = SharedMutex>
425 struct Synchronized : public SynchronizedBase<
426 Synchronized<T, Mutex>,
427 MutexLevelValue<Mutex>::value> {
430 SynchronizedBase<Synchronized<T, Mutex>, MutexLevelValue<Mutex>::value>;
431 static constexpr bool nxCopyCtor{
432 std::is_nothrow_copy_constructible<T>::value};
433 static constexpr bool nxMoveCtor{
434 std::is_nothrow_move_constructible<T>::value};
436 // used to disable copy construction and assignment
437 class NonImplementedType;
440 using LockedPtr = typename Base::LockedPtr;
441 using ConstLockedPtr = typename Base::ConstLockedPtr;
443 using MutexType = Mutex;
446 * Default constructor leaves both members call their own default
449 Synchronized() = default;
452 * Copy constructor copies the data (with locking the source and
453 * all) but does NOT copy the mutex. Doing so would result in
456 * Note that the copy constructor may throw because it acquires a lock in
457 * the contextualRLock() method
460 /* implicit */ Synchronized(typename std::conditional<
461 std::is_copy_constructible<T>::value,
463 NonImplementedType>::type rhs) /* may throw */
464 : Synchronized(rhs, rhs.contextualRLock()) {}
467 * Move constructor moves the data (with locking the source and all)
468 * but does not move the mutex.
470 * Note that the move constructor may throw because it acquires a lock.
471 * Since the move constructor is not declared noexcept, when objects of this
472 * class are used as elements in a vector or a similar container. The
473 * elements might not be moved around when resizing. They might be copied
474 * instead. You have been warned.
476 Synchronized(Synchronized&& rhs) /* may throw */
477 : Synchronized(std::move(rhs), rhs.contextualLock()) {}
480 * Constructor taking a datum as argument copies it. There is no
481 * need to lock the constructing object.
483 explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
486 * Constructor taking a datum rvalue as argument moves it. Again,
487 * there is no need to lock the constructing object.
489 explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
490 : datum_(std::move(rhs)) {}
493 * Lets you construct non-movable types in-place. Use the constexpr
494 * instance `in_place` as the first argument.
496 template <typename... Args>
497 explicit Synchronized(in_place_t, Args&&... args)
498 : datum_(std::forward<Args>(args)...) {}
501 * The canonical assignment operator only assigns the data, NOT the
502 * mutex. It locks the two objects in ascending order of their
505 Synchronized& operator=(typename std::conditional<
506 std::is_copy_assignable<T>::value,
508 NonImplementedType>::type rhs) {
510 // Self-assignment, pass.
511 } else if (this < &rhs) {
512 auto guard1 = operator->();
513 auto guard2 = rhs.operator->();
516 auto guard1 = rhs.operator->();
517 auto guard2 = operator->();
524 * Move assignment operator, only assigns the data, NOT the
525 * mutex. It locks the two objects in ascending order of their
528 Synchronized& operator=(Synchronized&& rhs) {
530 // Self-assignment, pass.
531 } else if (this < &rhs) {
532 auto guard1 = operator->();
533 auto guard2 = rhs.operator->();
534 datum_ = std::move(rhs.datum_);
536 auto guard1 = rhs.operator->();
537 auto guard2 = operator->();
538 datum_ = std::move(rhs.datum_);
544 * Lock object, assign datum.
546 Synchronized& operator=(const T& rhs) {
547 auto guard = operator->();
553 * Lock object, move-assign datum.
555 Synchronized& operator=(T&& rhs) {
556 auto guard = operator->();
557 datum_ = std::move(rhs);
562 * Acquire an appropriate lock based on the context.
564 * If the mutex is a shared mutex, and the Synchronized instance is const,
565 * this acquires a shared lock. Otherwise this acquires an exclusive lock.
567 * In general, prefer using the explicit rlock() and wlock() methods
568 * for read-write locks, and lock() for purely exclusive locks.
570 * contextualLock() is primarily intended for use in other template functions
571 * that do not necessarily know the lock type.
573 LockedPtr contextualLock() {
574 return LockedPtr(this);
576 ConstLockedPtr contextualLock() const {
577 return ConstLockedPtr(this);
579 template <class Rep, class Period>
580 LockedPtr contextualLock(const std::chrono::duration<Rep, Period>& timeout) {
581 return LockedPtr(this, timeout);
583 template <class Rep, class Period>
584 ConstLockedPtr contextualLock(
585 const std::chrono::duration<Rep, Period>& timeout) const {
586 return ConstLockedPtr(this, timeout);
589 * contextualRLock() acquires a read lock if the mutex type is shared,
590 * or a regular exclusive lock for non-shared mutex types.
592 * contextualRLock() when you know that you prefer a read lock (if
593 * available), even if the Synchronized<T> object itself is non-const.
595 ConstLockedPtr contextualRLock() const {
596 return ConstLockedPtr(this);
598 template <class Rep, class Period>
599 ConstLockedPtr contextualRLock(
600 const std::chrono::duration<Rep, Period>& timeout) const {
601 return ConstLockedPtr(this, timeout);
605 * This accessor offers a LockedPtr. In turn, LockedPtr offers
606 * operator-> returning a pointer to T. The operator-> keeps
607 * expanding until it reaches a pointer, so syncobj->foo() will lock
608 * the object and call foo() against it.
610 * NOTE: This API is planned to be deprecated in an upcoming diff.
611 * Prefer using lock(), wlock(), or rlock() instead.
613 LockedPtr operator->() {
614 return LockedPtr(this);
618 * Obtain a ConstLockedPtr.
620 * NOTE: This API is planned to be deprecated in an upcoming diff.
621 * Prefer using lock(), wlock(), or rlock() instead.
623 ConstLockedPtr operator->() const {
624 return ConstLockedPtr(this);
628 * Attempts to acquire for a given number of milliseconds. If
629 * acquisition is unsuccessful, the returned LockedPtr is nullptr.
631 * NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead.
632 * In the future it will be marked with a deprecation attribute to emit
633 * build-time warnings, and then it will be removed entirely.
635 LockedPtr timedAcquire(unsigned int milliseconds) {
636 return LockedPtr(this, std::chrono::milliseconds(milliseconds));
640 * Attempts to acquire for a given number of milliseconds. If
641 * acquisition is unsuccessful, the returned ConstLockedPtr is nullptr.
643 * NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead.
644 * In the future it will be marked with a deprecation attribute to emit
645 * build-time warnings, and then it will be removed entirely.
647 ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
648 return ConstLockedPtr(this, std::chrono::milliseconds(milliseconds));
652 * Sometimes, although you have a mutable object, you only want to
653 * call a const method against it. The most efficient way to achieve
654 * that is by using a read lock. You get to do so by using
655 * obj.asConst()->method() instead of obj->method().
657 * NOTE: This API is planned to be deprecated in an upcoming diff.
658 * Use rlock() instead.
660 const Synchronized& asConst() const {
665 * Swaps with another Synchronized. Protected against
666 * self-swap. Only data is swapped. Locks are acquired in increasing
669 void swap(Synchronized& rhs) {
674 return rhs.swap(*this);
676 auto guard1 = operator->();
677 auto guard2 = rhs.operator->();
680 swap(datum_, rhs.datum_);
684 * Swap with another datum. Recommended because it keeps the mutex
688 LockedPtr guard(this);
695 * Assign another datum and return the original value. Recommended
696 * because it keeps the mutex held only briefly.
698 T exchange(T&& rhs) {
700 return std::move(rhs);
704 * Copies datum to a given target.
706 void copy(T* target) const {
707 ConstLockedPtr guard(this);
712 * Returns a fresh copy of the datum.
715 ConstLockedPtr guard(this);
720 template <class LockedType, class MutexType, class LockPolicy>
721 friend class folly::LockedPtrBase;
722 template <class LockedType, class LockPolicy>
723 friend class folly::LockedPtr;
724 template <class LockedType, class LockPolicy>
725 friend class folly::LockedGuardPtr;
728 * Helper constructors to enable Synchronized for
729 * non-default constructible types T.
730 * Guards are created in actual public constructors and are alive
731 * for the time required to construct the object
734 const Synchronized& rhs,
735 const ConstLockedPtr& /*guard*/) noexcept(nxCopyCtor)
736 : datum_(rhs.datum_) {}
738 Synchronized(Synchronized&& rhs, const LockedPtr& /*guard*/) noexcept(
740 : datum_(std::move(rhs.datum_)) {}
742 // Synchronized data members
744 mutable Mutex mutex_;
747 template <class SynchronizedType, class LockPolicy>
748 class ScopedUnlocker;
752 * A helper alias that resolves to "const T" if the template parameter
753 * is a const Synchronized<T>, or "T" if the parameter is not const.
755 template <class SynchronizedType>
756 using SynchronizedDataType = typename std::conditional<
757 std::is_const<SynchronizedType>::value,
758 typename SynchronizedType::DataType const,
759 typename SynchronizedType::DataType>::type;
761 * A helper alias that resolves to a ConstLockedPtr if the template parameter
762 * is a const Synchronized<T>, or a LockedPtr if the parameter is not const.
764 template <class SynchronizedType>
765 using LockedPtrType = typename std::conditional<
766 std::is_const<SynchronizedType>::value,
767 typename SynchronizedType::ConstLockedPtr,
768 typename SynchronizedType::LockedPtr>::type;
769 } // namespace detail
772 * A helper base class for implementing LockedPtr.
774 * The main reason for having this as a separate class is so we can specialize
775 * it for std::mutex, so we can expose a std::unique_lock to the caller
776 * when std::mutex is being used. This allows callers to use a
777 * std::condition_variable with the mutex from a Synchronized<T, std::mutex>.
779 * We don't use std::unique_lock with other Mutex types since it makes the
780 * LockedPtr class slightly larger, and it makes the logic to support
781 * ScopedUnlocker slightly more complicated. std::mutex is the only one that
782 * really seems to benefit from the unique_lock. std::condition_variable
783 * itself only supports std::unique_lock<std::mutex>, so there doesn't seem to
784 * be any real benefit to exposing the unique_lock with other mutex types.
786 * Note that the SynchronizedType template parameter may or may not be const
789 template <class SynchronizedType, class Mutex, class LockPolicy>
790 class LockedPtrBase {
792 using MutexType = Mutex;
793 friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
796 * Destructor releases.
800 LockPolicy::unlock(parent_->mutex_);
805 * Unlock the synchronized data.
807 * The LockedPtr can no longer be dereferenced after unlock() has been
808 * called. isValid() will return false on an unlocked LockedPtr.
810 * unlock() can only be called on a LockedPtr that is valid.
813 DCHECK(parent_ != nullptr);
814 LockPolicy::unlock(parent_->mutex_);
820 explicit LockedPtrBase(SynchronizedType* parent) : parent_(parent) {
821 LockPolicy::lock(parent_->mutex_);
823 template <class Rep, class Period>
825 SynchronizedType* parent,
826 const std::chrono::duration<Rep, Period>& timeout) {
827 if (LockPolicy::try_lock_for(parent->mutex_, timeout)) {
828 this->parent_ = parent;
831 LockedPtrBase(LockedPtrBase&& rhs) noexcept : parent_(rhs.parent_) {
832 rhs.parent_ = nullptr;
834 LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
836 LockPolicy::unlock(parent_->mutex_);
839 parent_ = rhs.parent_;
840 rhs.parent_ = nullptr;
844 using UnlockerData = SynchronizedType*;
847 * Get a pointer to the Synchronized object from the UnlockerData.
849 * In the generic case UnlockerData is just the Synchronized pointer,
850 * so we return it as is. (This function is more interesting in the
851 * std::mutex specialization below.)
853 static SynchronizedType* getSynchronized(UnlockerData data) {
857 UnlockerData releaseLock() {
858 DCHECK(parent_ != nullptr);
859 auto current = parent_;
861 LockPolicy::unlock(current->mutex_);
864 void reacquireLock(UnlockerData&& data) {
865 DCHECK(parent_ == nullptr);
867 LockPolicy::lock(parent_->mutex_);
870 SynchronizedType* parent_ = nullptr;
874 * LockedPtrBase specialization for use with std::mutex.
876 * When std::mutex is used we use a std::unique_lock to hold the mutex.
877 * This makes it possible to use std::condition_variable with a
878 * Synchronized<T, std::mutex>.
880 template <class SynchronizedType, class LockPolicy>
881 class LockedPtrBase<SynchronizedType, std::mutex, LockPolicy> {
883 using MutexType = std::mutex;
884 friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
887 * Destructor releases.
890 // The std::unique_lock will automatically release the lock when it is
891 // destroyed, so we don't need to do anything extra here.
894 LockedPtrBase(LockedPtrBase&& rhs) noexcept
895 : lock_(std::move(rhs.lock_)), parent_(rhs.parent_) {
896 rhs.parent_ = nullptr;
898 LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
899 lock_ = std::move(rhs.lock_);
900 parent_ = rhs.parent_;
901 rhs.parent_ = nullptr;
906 * Get a reference to the std::unique_lock.
908 * This is provided so that callers can use Synchronized<T, std::mutex>
909 * with a std::condition_variable.
911 * While this API could be used to bypass the normal Synchronized APIs and
912 * manually interact with the underlying unique_lock, this is strongly
915 std::unique_lock<std::mutex>& getUniqueLock() {
920 * Unlock the synchronized data.
922 * The LockedPtr can no longer be dereferenced after unlock() has been
923 * called. isValid() will return false on an unlocked LockedPtr.
925 * unlock() can only be called on a LockedPtr that is valid.
928 DCHECK(parent_ != nullptr);
935 explicit LockedPtrBase(SynchronizedType* parent)
936 : lock_(parent->mutex_), parent_(parent) {}
939 std::pair<std::unique_lock<std::mutex>, SynchronizedType*>;
941 static SynchronizedType* getSynchronized(const UnlockerData& data) {
945 UnlockerData releaseLock() {
946 DCHECK(parent_ != nullptr);
947 UnlockerData data(std::move(lock_), parent_);
952 void reacquireLock(UnlockerData&& data) {
953 lock_ = std::move(data.first);
955 parent_ = data.second;
958 // The specialization for std::mutex does have to store slightly more
959 // state than the default implementation.
960 std::unique_lock<std::mutex> lock_;
961 SynchronizedType* parent_ = nullptr;
965 * This class temporarily unlocks a LockedPtr in a scoped manner.
967 template <class SynchronizedType, class LockPolicy>
968 class ScopedUnlocker {
970 explicit ScopedUnlocker(LockedPtr<SynchronizedType, LockPolicy>* p)
971 : ptr_(p), data_(ptr_->releaseLock()) {}
972 ScopedUnlocker(const ScopedUnlocker&) = delete;
973 ScopedUnlocker& operator=(const ScopedUnlocker&) = delete;
974 ScopedUnlocker(ScopedUnlocker&& other) noexcept
975 : ptr_(other.ptr_), data_(std::move(other.data_)) {
976 other.ptr_ = nullptr;
978 ScopedUnlocker& operator=(ScopedUnlocker&& other) = delete;
982 ptr_->reacquireLock(std::move(data_));
987 * Return a pointer to the Synchronized object used by this ScopedUnlocker.
989 SynchronizedType* getSynchronized() const {
990 return LockedPtr<SynchronizedType, LockPolicy>::getSynchronized(data_);
994 using Data = typename LockedPtr<SynchronizedType, LockPolicy>::UnlockerData;
995 LockedPtr<SynchronizedType, LockPolicy>* ptr_{nullptr};
1000 * A LockedPtr keeps a Synchronized<T> object locked for the duration of
1001 * LockedPtr's existence.
1003 * It provides access the datum's members directly by using operator->() and
1006 * The LockPolicy parameter controls whether or not the lock is acquired in
1007 * exclusive or shared mode.
1009 template <class SynchronizedType, class LockPolicy>
1010 class LockedPtr : public LockedPtrBase<
1012 typename SynchronizedType::MutexType,
1015 using Base = LockedPtrBase<
1017 typename SynchronizedType::MutexType,
1019 using UnlockerData = typename Base::UnlockerData;
1020 // CDataType is the DataType with the appropriate const-qualification
1021 using CDataType = detail::SynchronizedDataType<SynchronizedType>;
1024 using DataType = typename SynchronizedType::DataType;
1025 using MutexType = typename SynchronizedType::MutexType;
1026 using Synchronized = typename std::remove_const<SynchronizedType>::type;
1027 friend class ScopedUnlocker<SynchronizedType, LockPolicy>;
1030 * Creates an uninitialized LockedPtr.
1032 * Dereferencing an uninitialized LockedPtr is not allowed.
1037 * Takes a Synchronized<T> and locks it.
1039 explicit LockedPtr(SynchronizedType* parent) : Base(parent) {}
1042 * Takes a Synchronized<T> and attempts to lock it, within the specified
1045 * Blocks until the lock is acquired or until the specified timeout expires.
1046 * If the timeout expired without acquiring the lock, the LockedPtr will be
1047 * null, and LockedPtr::isNull() will return true.
1049 template <class Rep, class Period>
1051 SynchronizedType* parent,
1052 const std::chrono::duration<Rep, Period>& timeout)
1053 : Base(parent, timeout) {}
1058 LockedPtr(LockedPtr&& rhs) noexcept = default;
1061 * Move assignment operator.
1063 LockedPtr& operator=(LockedPtr&& rhs) noexcept = default;
1066 * Copy constructor and assignment operator are deleted.
1068 LockedPtr(const LockedPtr& rhs) = delete;
1069 LockedPtr& operator=(const LockedPtr& rhs) = delete;
1072 * Destructor releases.
1077 * Check if this LockedPtr is uninitialized, or points to valid locked data.
1079 * This method can be used to check if a timed-acquire operation succeeded.
1080 * If an acquire operation times out it will result in a null LockedPtr.
1082 * A LockedPtr is always either null, or holds a lock to valid data.
1083 * Methods such as scopedUnlock() reset the LockedPtr to null for the
1084 * duration of the unlock.
1086 bool isNull() const {
1087 return this->parent_ == nullptr;
1091 * Explicit boolean conversion.
1095 explicit operator bool() const {
1096 return this->parent_ != nullptr;
1100 * Access the locked data.
1102 * This method should only be used if the LockedPtr is valid.
1104 CDataType* operator->() const {
1105 return &this->parent_->datum_;
1109 * Access the locked data.
1111 * This method should only be used if the LockedPtr is valid.
1113 CDataType& operator*() const {
1114 return this->parent_->datum_;
1118 * Temporarily unlock the LockedPtr, and reset it to null.
1120 * Returns an helper object that will re-lock and restore the LockedPtr when
1121 * the helper is destroyed. The LockedPtr may not be dereferenced for as
1122 * long as this helper object exists.
1124 ScopedUnlocker<SynchronizedType, LockPolicy> scopedUnlock() {
1125 return ScopedUnlocker<SynchronizedType, LockPolicy>(this);
1128 /***************************************************************************
1129 * Upgradable lock methods.
1130 * These are disabled via SFINAE when the mutex is not upgradable
1131 **************************************************************************/
1133 * Move the locked ptr from an upgrade state to an exclusive state. The
1134 * current lock is left in a null state.
1137 typename SyncType = SynchronizedType,
1138 typename = typename std::enable_if<
1139 LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
1140 LockedPtr<SynchronizedType, LockPolicyFromUpgradeToExclusive>
1141 moveFromUpgradeToWrite() {
1142 auto* parent_to_pass_on = this->parent_;
1143 this->parent_ = nullptr;
1144 return LockedPtr<SynchronizedType, LockPolicyFromUpgradeToExclusive>(
1149 * Move the locked ptr from an exclusive state to an upgrade state. The
1150 * current lock is left in a null state.
1153 typename SyncType = SynchronizedType,
1154 typename = typename std::enable_if<
1155 LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
1156 LockedPtr<SynchronizedType, LockPolicyFromExclusiveToUpgrade>
1157 moveFromWriteToUpgrade() {
1158 auto* parent_to_pass_on = this->parent_;
1159 this->parent_ = nullptr;
1160 return LockedPtr<SynchronizedType, LockPolicyFromExclusiveToUpgrade>(
1165 * Move the locked ptr from an upgrade state to a shared state. The
1166 * current lock is left in a null state.
1169 typename SyncType = SynchronizedType,
1170 typename = typename std::enable_if<
1171 LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
1172 LockedPtr<SynchronizedType, LockPolicyFromUpgradeToShared>
1173 moveFromUpgradeToRead() {
1174 auto* parent_to_pass_on = this->parent_;
1175 this->parent_ = nullptr;
1176 return LockedPtr<SynchronizedType, LockPolicyFromUpgradeToShared>(
1181 * Move the locked ptr from an exclusive state to a shared state. The
1182 * current lock is left in a null state.
1185 typename SyncType = SynchronizedType,
1186 typename = typename std::enable_if<
1187 LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
1188 LockedPtr<SynchronizedType, LockPolicyFromExclusiveToShared>
1189 moveFromWriteToRead() {
1190 auto* parent_to_pass_on = this->parent_;
1191 this->parent_ = nullptr;
1192 return LockedPtr<SynchronizedType, LockPolicyFromExclusiveToShared>(
1198 * LockedGuardPtr is a simplified version of LockedPtr.
1200 * It is non-movable, and supports fewer features than LockedPtr. However, it
1201 * is ever-so-slightly more performant than LockedPtr. (The destructor can
1202 * unconditionally release the lock, without requiring a conditional branch.)
1204 * The relationship between LockedGuardPtr and LockedPtr is similar to that
1205 * between std::lock_guard and std::unique_lock.
1207 template <class SynchronizedType, class LockPolicy>
1208 class LockedGuardPtr {
1210 // CDataType is the DataType with the appropriate const-qualification
1211 using CDataType = detail::SynchronizedDataType<SynchronizedType>;
1214 using DataType = typename SynchronizedType::DataType;
1215 using MutexType = typename SynchronizedType::MutexType;
1216 using Synchronized = typename std::remove_const<SynchronizedType>::type;
1218 LockedGuardPtr() = delete;
1221 * Takes a Synchronized<T> and locks it.
1223 explicit LockedGuardPtr(SynchronizedType* parent) : parent_(parent) {
1224 LockPolicy::lock(parent_->mutex_);
1228 * Destructor releases.
1231 LockPolicy::unlock(parent_->mutex_);
1235 * Access the locked data.
1237 CDataType* operator->() const {
1238 return &parent_->datum_;
1242 * Access the locked data.
1244 CDataType& operator*() const {
1245 return parent_->datum_;
1249 // This is the entire state of LockedGuardPtr.
1250 SynchronizedType* const parent_{nullptr};
1254 * Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
1257 * The locks are acquired in order from lowest address to highest address.
1258 * (Note that this is not necessarily the same algorithm used by std::lock().)
1260 * For parameters that are const and support shared locks, a read lock is
1261 * acquired. Otherwise an exclusive lock is acquired.
1263 * TODO: Extend acquireLocked() with variadic template versions that
1264 * allow for more than 2 Synchronized arguments. (I haven't given too much
1265 * thought about how to implement this. It seems like it would be rather
1266 * complicated, but I think it should be possible.)
1268 template <class Sync1, class Sync2>
1269 std::tuple<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
1270 acquireLocked(Sync1& l1, Sync2& l2) {
1271 if (static_cast<const void*>(&l1) < static_cast<const void*>(&l2)) {
1272 auto p1 = l1.contextualLock();
1273 auto p2 = l2.contextualLock();
1274 return std::make_tuple(std::move(p1), std::move(p2));
1276 auto p2 = l2.contextualLock();
1277 auto p1 = l1.contextualLock();
1278 return std::make_tuple(std::move(p1), std::move(p2));
1283 * A version of acquireLocked() that returns a std::pair rather than a
1284 * std::tuple, which is easier to use in many places.
1286 template <class Sync1, class Sync2>
1287 std::pair<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
1288 acquireLockedPair(Sync1& l1, Sync2& l2) {
1289 auto lockedPtrs = acquireLocked(l1, l2);
1290 return {std::move(std::get<0>(lockedPtrs)),
1291 std::move(std::get<1>(lockedPtrs))};
1294 /************************************************************************
1295 * NOTE: All APIs below this line will be deprecated in upcoming diffs.
1296 ************************************************************************/
1298 // Non-member swap primitive
1299 template <class T, class M>
1300 void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
1305 * Disambiguate the name var by concatenating the line number of the original
1306 * point of expansion. This avoids shadowing warnings for nested
1307 * SYNCHRONIZEDs. The name is consistent if used multiple times within
1309 * Only for internal use.
1311 #define SYNCHRONIZED_VAR(var) FB_CONCATENATE(SYNCHRONIZED_##var##_, __LINE__)
1314 * SYNCHRONIZED is the main facility that makes Synchronized<T>
1315 * helpful. It is a pseudo-statement that introduces a scope where the
1316 * object is locked. Inside that scope you get to access the unadorned
1321 * Synchronized<vector<int>> svector;
1323 * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
1325 * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
1327 * Refer to folly/docs/Synchronized.md for a detailed explanation and more
1330 #define SYNCHRONIZED(...) \
1331 FOLLY_PUSH_WARNING \
1332 FOLLY_GCC_DISABLE_WARNING("-Wshadow") \
1333 FOLLY_MSVC_DISABLE_WARNING(4189) /* initialized but unreferenced */ \
1334 FOLLY_MSVC_DISABLE_WARNING(4456) /* declaration hides local */ \
1335 FOLLY_MSVC_DISABLE_WARNING(4457) /* declaration hides parameter */ \
1336 FOLLY_MSVC_DISABLE_WARNING(4458) /* declaration hides member */ \
1337 FOLLY_MSVC_DISABLE_WARNING(4459) /* declaration hides global */ \
1338 FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS \
1339 if (bool SYNCHRONIZED_VAR(state) = false) { \
1341 for (auto SYNCHRONIZED_VAR(lockedPtr) = \
1342 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \
1343 !SYNCHRONIZED_VAR(state); \
1344 SYNCHRONIZED_VAR(state) = true) \
1345 for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
1346 *SYNCHRONIZED_VAR(lockedPtr).operator->(); \
1347 !SYNCHRONIZED_VAR(state); \
1348 SYNCHRONIZED_VAR(state) = true) \
1351 #define TIMED_SYNCHRONIZED(timeout, ...) \
1352 if (bool SYNCHRONIZED_VAR(state) = false) { \
1354 for (auto SYNCHRONIZED_VAR(lockedPtr) = \
1355 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \
1356 !SYNCHRONIZED_VAR(state); \
1357 SYNCHRONIZED_VAR(state) = true) \
1358 for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
1359 (!SYNCHRONIZED_VAR(lockedPtr) \
1361 : SYNCHRONIZED_VAR(lockedPtr).operator->()); \
1362 !SYNCHRONIZED_VAR(state); \
1363 SYNCHRONIZED_VAR(state) = true)
1366 * Similar to SYNCHRONIZED, but only uses a read lock.
1368 #define SYNCHRONIZED_CONST(...) \
1370 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
1371 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
1374 * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
1376 #define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
1377 TIMED_SYNCHRONIZED( \
1379 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
1380 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
1383 * Synchronizes two Synchronized objects (they may encapsulate
1384 * different data). Synchronization is done in increasing address of
1385 * object order, so there is no deadlock risk.
1387 #define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
1388 if (bool SYNCHRONIZED_VAR(state) = false) { \
1390 for (auto SYNCHRONIZED_VAR(ptrs) = acquireLockedPair(e1, e2); \
1391 !SYNCHRONIZED_VAR(state); \
1392 SYNCHRONIZED_VAR(state) = true) \
1393 for (auto& n1 = *SYNCHRONIZED_VAR(ptrs).first; !SYNCHRONIZED_VAR(state); \
1394 SYNCHRONIZED_VAR(state) = true) \
1395 for (auto& n2 = *SYNCHRONIZED_VAR(ptrs).second; \
1396 !SYNCHRONIZED_VAR(state); \
1397 SYNCHRONIZED_VAR(state) = true)
1399 } /* namespace folly */