2 * Copyright 2016 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/LockTraits.h>
29 #include <folly/Preprocessor.h>
30 #include <folly/SharedMutex.h>
31 #include <folly/Traits.h>
32 #include <glog/logging.h>
34 #include <type_traits>
38 template <class LockedType, class Mutex, class LockPolicy>
40 template <class LockedType, class LockPolicy>
42 template <class LockedType, class LockPolicy = LockPolicyExclusive>
46 * SynchronizedBase is a helper parent class for Synchronized<T>.
48 * It provides wlock() and rlock() methods for shared mutex types,
49 * or lock() methods for purely exclusive mutex types.
51 template <class Subclass, bool is_shared>
52 class SynchronizedBase;
55 * SynchronizedBase specialization for shared mutex types.
57 * This class provides wlock() and rlock() methods for acquiring the lock and
60 template <class Subclass>
61 class SynchronizedBase<Subclass, true> {
63 using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
64 using ConstWLockedPtr =
65 ::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
66 using ConstLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyShared>;
69 * Acquire an exclusive lock, and return a LockedPtr that can be used to
70 * safely access the datum.
72 * LockedPtr offers operator -> and * to provide access to the datum.
73 * The lock will be released when the LockedPtr is destroyed.
76 return LockedPtr(static_cast<Subclass*>(this));
78 ConstWLockedPtr wlock() const {
79 return ConstWLockedPtr(static_cast<const Subclass*>(this));
83 * Acquire a read lock, and return a ConstLockedPtr that can be used to
84 * safely access the datum.
86 ConstLockedPtr rlock() const {
87 return ConstLockedPtr(static_cast<const Subclass*>(this));
91 * Attempts to acquire the lock, or fails if the timeout elapses first.
92 * If acquisition is unsuccessful, the returned LockedPtr will be null.
94 * (Use LockedPtr::isNull() to check for validity.)
96 template <class Rep, class Period>
97 LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) {
98 return LockedPtr(static_cast<Subclass*>(this), timeout);
100 template <class Rep, class Period>
101 ConstWLockedPtr wlock(
102 const std::chrono::duration<Rep, Period>& timeout) const {
103 return ConstWLockedPtr(static_cast<const Subclass*>(this), timeout);
107 * Attempts to acquire the lock, or fails if the timeout elapses first.
108 * If acquisition is unsuccessful, the returned LockedPtr will be null.
110 * (Use LockedPtr::isNull() to check for validity.)
112 template <class Rep, class Period>
113 ConstLockedPtr rlock(
114 const std::chrono::duration<Rep, Period>& timeout) const {
115 return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
119 * Note: C++ 17 adds guaranteed copy elision. (http://wg21.link/P0135)
120 * Once compilers support this, it would be nice to add wguard() and rguard()
121 * methods that return LockedGuardPtr objects.
125 * Invoke a function while holding the lock exclusively.
127 * A reference to the datum will be passed into the function as its only
130 * This can be used with a lambda argument for easily defining small critical
131 * sections in the code. For example:
133 * auto value = obj.withWLock([](auto& data) {
135 * return data.getValue();
138 template <class Function>
139 auto withWLock(Function&& function) {
140 LockedGuardPtr<Subclass, LockPolicyExclusive> guardPtr(
141 static_cast<Subclass*>(this));
142 return function(*guardPtr);
144 template <class Function>
145 auto withWLock(Function&& function) const {
146 LockedGuardPtr<const Subclass, LockPolicyExclusive> guardPtr(
147 static_cast<const Subclass*>(this));
148 return function(*guardPtr);
152 * Invoke a function while holding the lock exclusively.
154 * This is similar to withWLock(), but the function will be passed a
155 * LockedPtr rather than a reference to the data itself.
157 * This allows scopedUnlock() to be called on the LockedPtr argument if
160 template <class Function>
161 auto withWLockPtr(Function&& function) {
162 return function(wlock());
164 template <class Function>
165 auto withWLockPtr(Function&& function) const {
166 return function(wlock());
170 * Invoke a function while holding an the lock in shared mode.
172 * A const reference to the datum will be passed into the function as its
175 template <class Function>
176 auto withRLock(Function&& function) const {
177 LockedGuardPtr<const Subclass, LockPolicyShared> guardPtr(
178 static_cast<const Subclass*>(this));
179 return function(*guardPtr);
182 template <class Function>
183 auto withRLockPtr(Function&& function) const {
184 return function(rlock());
189 * SynchronizedBase specialization for non-shared mutex types.
191 * This class provides lock() methods for acquiring the lock and accessing the
194 template <class Subclass>
195 class SynchronizedBase<Subclass, false> {
197 using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
198 using ConstLockedPtr =
199 ::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
202 * Acquire a lock, and return a LockedPtr that can be used to safely access
206 return LockedPtr(static_cast<Subclass*>(this));
210 * Acquire a lock, and return a ConstLockedPtr that can be used to safely
213 ConstLockedPtr lock() const {
214 return ConstLockedPtr(static_cast<const Subclass*>(this));
218 * Attempts to acquire the lock, or fails if the timeout elapses first.
219 * If acquisition is unsuccessful, the returned LockedPtr will be null.
221 template <class Rep, class Period>
222 LockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) {
223 return LockedPtr(static_cast<Subclass*>(this), timeout);
227 * Attempts to acquire the lock, or fails if the timeout elapses first.
228 * If acquisition is unsuccessful, the returned LockedPtr will be null.
230 template <class Rep, class Period>
231 ConstLockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) const {
232 return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
236 * Note: C++ 17 adds guaranteed copy elision. (http://wg21.link/P0135)
237 * Once compilers support this, it would be nice to add guard() methods that
238 * return LockedGuardPtr objects.
242 * Invoke a function while holding the lock.
244 * A reference to the datum will be passed into the function as its only
247 * This can be used with a lambda argument for easily defining small critical
248 * sections in the code. For example:
250 * auto value = obj.withLock([](auto& data) {
252 * return data.getValue();
255 template <class Function>
256 auto withLock(Function&& function) {
257 LockedGuardPtr<Subclass, LockPolicyExclusive> guardPtr(
258 static_cast<Subclass*>(this));
259 return function(*guardPtr);
261 template <class Function>
262 auto withLock(Function&& function) const {
263 LockedGuardPtr<const Subclass, LockPolicyExclusive> guardPtr(
264 static_cast<const Subclass*>(this));
265 return function(*guardPtr);
269 * Invoke a function while holding the lock exclusively.
271 * This is similar to withWLock(), but the function will be passed a
272 * LockedPtr rather than a reference to the data itself.
274 * This allows scopedUnlock() and getUniqueLock() to be called on the
275 * LockedPtr argument.
277 template <class Function>
278 auto withLockPtr(Function&& function) {
279 return function(lock());
281 template <class Function>
282 auto withLockPtr(Function&& function) const {
283 return function(lock());
288 * Synchronized<T> encapsulates an object of type T (a "datum") paired
289 * with a mutex. The only way to access the datum is while the mutex
290 * is locked, and Synchronized makes it virtually impossible to do
291 * otherwise. The code that would access the datum in unsafe ways
292 * would look odd and convoluted, thus readily alerting the human
293 * reviewer. In contrast, the code that uses Synchronized<T> correctly
294 * looks simple and intuitive.
296 * The second parameter must be a mutex type. Any mutex type supported by
297 * LockTraits<Mutex> can be used. By default any class with lock() and
298 * unlock() methods will work automatically. LockTraits can be specialized to
299 * teach Synchronized how to use other custom mutex types. See the
300 * documentation in LockTraits.h for additional details.
302 * Supported mutexes that work by default include std::mutex,
303 * std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex,
304 * folly::SharedMutex, folly::RWSpinLock, and folly::SpinLock.
305 * Include LockTraitsBoost.h to get additional LockTraits specializations to
306 * support the following boost mutex types: boost::mutex,
307 * boost::recursive_mutex, boost::shared_mutex, boost::timed_mutex, and
308 * boost::recursive_timed_mutex.
310 template <class T, class Mutex = SharedMutex>
311 struct Synchronized : public SynchronizedBase<
312 Synchronized<T, Mutex>,
313 LockTraits<Mutex>::is_shared> {
316 SynchronizedBase<Synchronized<T, Mutex>, LockTraits<Mutex>::is_shared>;
317 static constexpr bool nxCopyCtor{
318 std::is_nothrow_copy_constructible<T>::value};
319 static constexpr bool nxMoveCtor{
320 std::is_nothrow_move_constructible<T>::value};
323 using LockedPtr = typename Base::LockedPtr;
324 using ConstLockedPtr = typename Base::ConstLockedPtr;
326 using MutexType = Mutex;
329 * Default constructor leaves both members call their own default
332 Synchronized() = default;
335 * Copy constructor copies the data (with locking the source and
336 * all) but does NOT copy the mutex. Doing so would result in
339 * Note that the copy constructor may throw because it acquires a lock in
340 * the contextualRLock() method
342 Synchronized(const Synchronized& rhs) /* may throw */
343 : Synchronized(rhs, rhs.contextualRLock()) {}
346 * Move constructor moves the data (with locking the source and all)
347 * but does not move the mutex.
349 * Note that the move constructor may throw because it acquires a lock.
350 * Since the move constructor is not declared noexcept, when objects of this
351 * class are used as elements in a vector or a similar container. The
352 * elements might not be moved around when resizing. They might be copied
353 * instead. You have been warned.
355 Synchronized(Synchronized&& rhs) /* may throw */
356 : Synchronized(std::move(rhs), rhs.contextualLock()) {}
359 * Constructor taking a datum as argument copies it. There is no
360 * need to lock the constructing object.
362 explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
365 * Constructor taking a datum rvalue as argument moves it. Again,
366 * there is no need to lock the constructing object.
368 explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
369 : datum_(std::move(rhs)) {}
372 * Lets you construct non-movable types in-place. Use the constexpr
373 * instance `construct_in_place` as the first argument.
375 template <typename... Args>
376 explicit Synchronized(construct_in_place_t, Args&&... args)
377 : datum_(std::forward<Args>(args)...) {}
380 * The canonical assignment operator only assigns the data, NOT the
381 * mutex. It locks the two objects in ascending order of their
384 Synchronized& operator=(const Synchronized& rhs) {
386 // Self-assignment, pass.
387 } else if (this < &rhs) {
388 auto guard1 = operator->();
389 auto guard2 = rhs.operator->();
392 auto guard1 = rhs.operator->();
393 auto guard2 = operator->();
400 * Move assignment operator, only assigns the data, NOT the
401 * mutex. It locks the two objects in ascending order of their
404 Synchronized& operator=(Synchronized&& rhs) {
406 // Self-assignment, pass.
407 } else if (this < &rhs) {
408 auto guard1 = operator->();
409 auto guard2 = rhs.operator->();
410 datum_ = std::move(rhs.datum_);
412 auto guard1 = rhs.operator->();
413 auto guard2 = operator->();
414 datum_ = std::move(rhs.datum_);
420 * Lock object, assign datum.
422 Synchronized& operator=(const T& rhs) {
423 auto guard = operator->();
429 * Lock object, move-assign datum.
431 Synchronized& operator=(T&& rhs) {
432 auto guard = operator->();
433 datum_ = std::move(rhs);
438 * Acquire an appropriate lock based on the context.
440 * If the mutex is a shared mutex, and the Synchronized instance is const,
441 * this acquires a shared lock. Otherwise this acquires an exclusive lock.
443 * In general, prefer using the explicit rlock() and wlock() methods
444 * for read-write locks, and lock() for purely exclusive locks.
446 * contextualLock() is primarily intended for use in other template functions
447 * that do not necessarily know the lock type.
449 LockedPtr contextualLock() {
450 return LockedPtr(this);
452 ConstLockedPtr contextualLock() const {
453 return ConstLockedPtr(this);
455 template <class Rep, class Period>
456 LockedPtr contextualLock(const std::chrono::duration<Rep, Period>& timeout) {
457 return LockedPtr(this, timeout);
459 template <class Rep, class Period>
460 ConstLockedPtr contextualLock(
461 const std::chrono::duration<Rep, Period>& timeout) const {
462 return ConstLockedPtr(this, timeout);
465 * contextualRLock() acquires a read lock if the mutex type is shared,
466 * or a regular exclusive lock for non-shared mutex types.
468 * contextualRLock() when you know that you prefer a read lock (if
469 * available), even if the Synchronized<T> object itself is non-const.
471 ConstLockedPtr contextualRLock() const {
472 return ConstLockedPtr(this);
474 template <class Rep, class Period>
475 ConstLockedPtr contextualRLock(
476 const std::chrono::duration<Rep, Period>& timeout) const {
477 return ConstLockedPtr(this, timeout);
481 * This accessor offers a LockedPtr. In turn, LockedPtr offers
482 * operator-> returning a pointer to T. The operator-> keeps
483 * expanding until it reaches a pointer, so syncobj->foo() will lock
484 * the object and call foo() against it.
486 * NOTE: This API is planned to be deprecated in an upcoming diff.
487 * Prefer using lock(), wlock(), or rlock() instead.
489 LockedPtr operator->() {
490 return LockedPtr(this);
494 * Obtain a ConstLockedPtr.
496 * NOTE: This API is planned to be deprecated in an upcoming diff.
497 * Prefer using lock(), wlock(), or rlock() instead.
499 ConstLockedPtr operator->() const {
500 return ConstLockedPtr(this);
504 * Attempts to acquire for a given number of milliseconds. If
505 * acquisition is unsuccessful, the returned LockedPtr is NULL.
507 * NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead.
508 * In the future it will be marked with a deprecation attribute to emit
509 * build-time warnings, and then it will be removed entirely.
511 LockedPtr timedAcquire(unsigned int milliseconds) {
512 return LockedPtr(this, std::chrono::milliseconds(milliseconds));
516 * Attempts to acquire for a given number of milliseconds. If
517 * acquisition is unsuccessful, the returned ConstLockedPtr is NULL.
519 * NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead.
520 * In the future it will be marked with a deprecation attribute to emit
521 * build-time warnings, and then it will be removed entirely.
523 ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
524 return ConstLockedPtr(this, std::chrono::milliseconds(milliseconds));
528 * Sometimes, although you have a mutable object, you only want to
529 * call a const method against it. The most efficient way to achieve
530 * that is by using a read lock. You get to do so by using
531 * obj.asConst()->method() instead of obj->method().
533 * NOTE: This API is planned to be deprecated in an upcoming diff.
534 * Use rlock() instead.
536 const Synchronized& asConst() const {
541 * Swaps with another Synchronized. Protected against
542 * self-swap. Only data is swapped. Locks are acquired in increasing
545 void swap(Synchronized& rhs) {
550 return rhs.swap(*this);
552 auto guard1 = operator->();
553 auto guard2 = rhs.operator->();
556 swap(datum_, rhs.datum_);
560 * Swap with another datum. Recommended because it keeps the mutex
564 LockedPtr guard(this);
571 * Copies datum to a given target.
573 void copy(T* target) const {
574 ConstLockedPtr guard(this);
579 * Returns a fresh copy of the datum.
582 ConstLockedPtr guard(this);
587 template <class LockedType, class MutexType, class LockPolicy>
588 friend class folly::LockedPtrBase;
589 template <class LockedType, class LockPolicy>
590 friend class folly::LockedPtr;
591 template <class LockedType, class LockPolicy>
592 friend class folly::LockedGuardPtr;
595 * Helper constructors to enable Synchronized for
596 * non-default constructible types T.
597 * Guards are created in actual public constructors and are alive
598 * for the time required to construct the object
601 const Synchronized& rhs,
602 const ConstLockedPtr& /*guard*/) noexcept(nxCopyCtor)
603 : datum_(rhs.datum_) {}
605 Synchronized(Synchronized&& rhs, const LockedPtr& /*guard*/) noexcept(
607 : datum_(std::move(rhs.datum_)) {}
609 // Synchronized data members
611 mutable Mutex mutex_;
614 template <class SynchronizedType, class LockPolicy>
615 class ScopedUnlocker;
619 * A helper alias that resolves to "const T" if the template parameter
620 * is a const Synchronized<T>, or "T" if the parameter is not const.
622 template <class SynchronizedType>
623 using SynchronizedDataType = typename std::conditional<
624 std::is_const<SynchronizedType>::value,
625 typename SynchronizedType::DataType const,
626 typename SynchronizedType::DataType>::type;
628 * A helper alias that resolves to a ConstLockedPtr if the template parameter
629 * is a const Synchronized<T>, or a LockedPtr if the parameter is not const.
631 template <class SynchronizedType>
632 using LockedPtrType = typename std::conditional<
633 std::is_const<SynchronizedType>::value,
634 typename SynchronizedType::ConstLockedPtr,
635 typename SynchronizedType::LockedPtr>::type;
639 * A helper base class for implementing LockedPtr.
641 * The main reason for having this as a separate class is so we can specialize
642 * it for std::mutex, so we can expose a std::unique_lock to the caller
643 * when std::mutex is being used. This allows callers to use a
644 * std::condition_variable with the mutex from a Synchronized<T, std::mutex>.
646 * We don't use std::unique_lock with other Mutex types since it makes the
647 * LockedPtr class slightly larger, and it makes the logic to support
648 * ScopedUnlocker slightly more complicated. std::mutex is the only one that
649 * really seems to benefit from the unique_lock. std::condition_variable
650 * itself only supports std::unique_lock<std::mutex>, so there doesn't seem to
651 * be any real benefit to exposing the unique_lock with other mutex types.
653 * Note that the SynchronizedType template parameter may or may not be const
656 template <class SynchronizedType, class Mutex, class LockPolicy>
657 class LockedPtrBase {
659 using MutexType = Mutex;
660 friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
663 * Destructor releases.
667 LockPolicy::unlock(parent_->mutex_);
672 * Unlock the synchronized data.
674 * The LockedPtr can no longer be dereferenced after unlock() has been
675 * called. isValid() will return false on an unlocked LockedPtr.
677 * unlock() can only be called on a LockedPtr that is valid.
680 DCHECK(parent_ != nullptr);
681 LockPolicy::unlock(parent_->mutex_);
687 explicit LockedPtrBase(SynchronizedType* parent) : parent_(parent) {
688 LockPolicy::lock(parent_->mutex_);
690 template <class Rep, class Period>
692 SynchronizedType* parent,
693 const std::chrono::duration<Rep, Period>& timeout) {
694 if (LockPolicy::try_lock_for(parent->mutex_, timeout)) {
695 this->parent_ = parent;
698 LockedPtrBase(LockedPtrBase&& rhs) noexcept : parent_(rhs.parent_) {
699 rhs.parent_ = nullptr;
701 LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
703 LockPolicy::unlock(parent_->mutex_);
706 parent_ = rhs.parent_;
707 rhs.parent_ = nullptr;
711 using UnlockerData = SynchronizedType*;
714 * Get a pointer to the Synchronized object from the UnlockerData.
716 * In the generic case UnlockerData is just the Synchronized pointer,
717 * so we return it as is. (This function is more interesting in the
718 * std::mutex specialization below.)
720 static SynchronizedType* getSynchronized(UnlockerData data) {
724 UnlockerData releaseLock() {
725 DCHECK(parent_ != nullptr);
726 auto current = parent_;
728 LockPolicy::unlock(current->mutex_);
731 void reacquireLock(UnlockerData&& data) {
732 DCHECK(parent_ == nullptr);
734 LockPolicy::lock(parent_->mutex_);
737 SynchronizedType* parent_ = nullptr;
741 * LockedPtrBase specialization for use with std::mutex.
743 * When std::mutex is used we use a std::unique_lock to hold the mutex.
744 * This makes it possible to use std::condition_variable with a
745 * Synchronized<T, std::mutex>.
747 template <class SynchronizedType, class LockPolicy>
748 class LockedPtrBase<SynchronizedType, std::mutex, LockPolicy> {
750 using MutexType = std::mutex;
751 friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
754 * Destructor releases.
757 // The std::unique_lock will automatically release the lock when it is
758 // destroyed, so we don't need to do anything extra here.
761 LockedPtrBase(LockedPtrBase&& rhs) noexcept
762 : lock_(std::move(rhs.lock_)), parent_(rhs.parent_) {
763 rhs.parent_ = nullptr;
765 LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
766 lock_ = std::move(rhs.lock_);
767 parent_ = rhs.parent_;
768 rhs.parent_ = nullptr;
773 * Get a reference to the std::unique_lock.
775 * This is provided so that callers can use Synchronized<T, std::mutex>
776 * with a std::condition_variable.
778 * While this API could be used to bypass the normal Synchronized APIs and
779 * manually interact with the underlying unique_lock, this is strongly
782 std::unique_lock<std::mutex>& getUniqueLock() {
787 * Unlock the synchronized data.
789 * The LockedPtr can no longer be dereferenced after unlock() has been
790 * called. isValid() will return false on an unlocked LockedPtr.
792 * unlock() can only be called on a LockedPtr that is valid.
795 DCHECK(parent_ != nullptr);
802 explicit LockedPtrBase(SynchronizedType* parent)
803 : lock_(parent->mutex_), parent_(parent) {}
806 std::pair<std::unique_lock<std::mutex>, SynchronizedType*>;
808 static SynchronizedType* getSynchronized(const UnlockerData& data) {
812 UnlockerData releaseLock() {
813 DCHECK(parent_ != nullptr);
814 UnlockerData data(std::move(lock_), parent_);
819 void reacquireLock(UnlockerData&& data) {
820 lock_ = std::move(data.first);
822 parent_ = data.second;
825 // The specialization for std::mutex does have to store slightly more
826 // state than the default implementation.
827 std::unique_lock<std::mutex> lock_;
828 SynchronizedType* parent_ = nullptr;
832 * This class temporarily unlocks a LockedPtr in a scoped manner.
834 template <class SynchronizedType, class LockPolicy>
835 class ScopedUnlocker {
837 explicit ScopedUnlocker(LockedPtr<SynchronizedType, LockPolicy>* p)
838 : ptr_(p), data_(ptr_->releaseLock()) {}
839 ScopedUnlocker(const ScopedUnlocker&) = delete;
840 ScopedUnlocker& operator=(const ScopedUnlocker&) = delete;
841 ScopedUnlocker(ScopedUnlocker&& other) noexcept
842 : ptr_(other.ptr_), data_(std::move(other.data_)) {
843 other.ptr_ = nullptr;
845 ScopedUnlocker& operator=(ScopedUnlocker&& other) = delete;
849 ptr_->reacquireLock(std::move(data_));
854 * Return a pointer to the Synchronized object used by this ScopedUnlocker.
856 SynchronizedType* getSynchronized() const {
857 return LockedPtr<SynchronizedType, LockPolicy>::getSynchronized(data_);
861 using Data = typename LockedPtr<SynchronizedType, LockPolicy>::UnlockerData;
862 LockedPtr<SynchronizedType, LockPolicy>* ptr_{nullptr};
867 * A LockedPtr keeps a Synchronized<T> object locked for the duration of
868 * LockedPtr's existence.
870 * It provides access the datum's members directly by using operator->() and
873 * The LockPolicy parameter controls whether or not the lock is acquired in
874 * exclusive or shared mode.
876 template <class SynchronizedType, class LockPolicy>
877 class LockedPtr : public LockedPtrBase<
879 typename SynchronizedType::MutexType,
882 using Base = LockedPtrBase<
884 typename SynchronizedType::MutexType,
886 using UnlockerData = typename Base::UnlockerData;
887 // CDataType is the DataType with the appropriate const-qualification
888 using CDataType = detail::SynchronizedDataType<SynchronizedType>;
891 using DataType = typename SynchronizedType::DataType;
892 using MutexType = typename SynchronizedType::MutexType;
893 using Synchronized = typename std::remove_const<SynchronizedType>::type;
894 friend class ScopedUnlocker<SynchronizedType, LockPolicy>;
897 * Creates an uninitialized LockedPtr.
899 * Dereferencing an uninitialized LockedPtr is not allowed.
904 * Takes a Synchronized<T> and locks it.
906 explicit LockedPtr(SynchronizedType* parent) : Base(parent) {}
909 * Takes a Synchronized<T> and attempts to lock it, within the specified
912 * Blocks until the lock is acquired or until the specified timeout expires.
913 * If the timeout expired without acquiring the lock, the LockedPtr will be
914 * null, and LockedPtr::isNull() will return true.
916 template <class Rep, class Period>
918 SynchronizedType* parent,
919 const std::chrono::duration<Rep, Period>& timeout)
920 : Base(parent, timeout) {}
925 LockedPtr(LockedPtr&& rhs) noexcept = default;
928 * Move assignment operator.
930 LockedPtr& operator=(LockedPtr&& rhs) noexcept = default;
933 * Copy constructor and assignment operator are deleted.
935 LockedPtr(const LockedPtr& rhs) = delete;
936 LockedPtr& operator=(const LockedPtr& rhs) = delete;
939 * Destructor releases.
944 * Check if this LockedPtr is uninitialized, or points to valid locked data.
946 * This method can be used to check if a timed-acquire operation succeeded.
947 * If an acquire operation times out it will result in a null LockedPtr.
949 * A LockedPtr is always either null, or holds a lock to valid data.
950 * Methods such as scopedUnlock() reset the LockedPtr to null for the
951 * duration of the unlock.
953 bool isNull() const {
954 return this->parent_ == nullptr;
958 * Explicit boolean conversion.
962 explicit operator bool() const {
963 return this->parent_ != nullptr;
967 * Access the locked data.
969 * This method should only be used if the LockedPtr is valid.
971 CDataType* operator->() const {
972 return &this->parent_->datum_;
976 * Access the locked data.
978 * This method should only be used if the LockedPtr is valid.
980 CDataType& operator*() const {
981 return this->parent_->datum_;
985 * Temporarily unlock the LockedPtr, and reset it to null.
987 * Returns an helper object that will re-lock and restore the LockedPtr when
988 * the helper is destroyed. The LockedPtr may not be dereferenced for as
989 * long as this helper object exists.
991 ScopedUnlocker<SynchronizedType, LockPolicy> scopedUnlock() {
992 return ScopedUnlocker<SynchronizedType, LockPolicy>(this);
997 * LockedGuardPtr is a simplified version of LockedPtr.
999 * It is non-movable, and supports fewer features than LockedPtr. However, it
1000 * is ever-so-slightly more performant than LockedPtr. (The destructor can
1001 * unconditionally release the lock, without requiring a conditional branch.)
1003 * The relationship between LockedGuardPtr and LockedPtr is similar to that
1004 * between std::lock_guard and std::unique_lock.
1006 template <class SynchronizedType, class LockPolicy>
1007 class LockedGuardPtr {
1009 // CDataType is the DataType with the appropriate const-qualification
1010 using CDataType = detail::SynchronizedDataType<SynchronizedType>;
1013 using DataType = typename SynchronizedType::DataType;
1014 using MutexType = typename SynchronizedType::MutexType;
1015 using Synchronized = typename std::remove_const<SynchronizedType>::type;
1017 LockedGuardPtr() = delete;
1020 * Takes a Synchronized<T> and locks it.
1022 explicit LockedGuardPtr(SynchronizedType* parent) : parent_(parent) {
1023 LockPolicy::lock(parent_->mutex_);
1027 * Destructor releases.
1030 LockPolicy::unlock(parent_->mutex_);
1034 * Access the locked data.
1036 CDataType* operator->() const {
1037 return &parent_->datum_;
1041 * Access the locked data.
1043 CDataType& operator*() const {
1044 return parent_->datum_;
1048 // This is the entire state of LockedGuardPtr.
1049 SynchronizedType* const parent_{nullptr};
1053 * Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
1056 * The locks are acquired in order from lowest address to highest address.
1057 * (Note that this is not necessarily the same algorithm used by std::lock().)
1059 * For parameters that are const and support shared locks, a read lock is
1060 * acquired. Otherwise an exclusive lock is acquired.
1062 * TODO: Extend acquireLocked() with variadic template versions that
1063 * allow for more than 2 Synchronized arguments. (I haven't given too much
1064 * thought about how to implement this. It seems like it would be rather
1065 * complicated, but I think it should be possible.)
1067 template <class Sync1, class Sync2>
1068 std::tuple<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
1069 acquireLocked(Sync1& l1, Sync2& l2) {
1070 if (static_cast<const void*>(&l1) < static_cast<const void*>(&l2)) {
1071 auto p1 = l1.contextualLock();
1072 auto p2 = l2.contextualLock();
1073 return std::make_tuple(std::move(p1), std::move(p2));
1075 auto p2 = l2.contextualLock();
1076 auto p1 = l1.contextualLock();
1077 return std::make_tuple(std::move(p1), std::move(p2));
1082 * A version of acquireLocked() that returns a std::pair rather than a
1083 * std::tuple, which is easier to use in many places.
1085 template <class Sync1, class Sync2>
1086 std::pair<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
1087 acquireLockedPair(Sync1& l1, Sync2& l2) {
1088 auto lockedPtrs = acquireLocked(l1, l2);
1089 return {std::move(std::get<0>(lockedPtrs)),
1090 std::move(std::get<1>(lockedPtrs))};
1093 /************************************************************************
1094 * NOTE: All APIs below this line will be deprecated in upcoming diffs.
1095 ************************************************************************/
1097 // Non-member swap primitive
1098 template <class T, class M>
1099 void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
1104 * SYNCHRONIZED is the main facility that makes Synchronized<T>
1105 * helpful. It is a pseudo-statement that introduces a scope where the
1106 * object is locked. Inside that scope you get to access the unadorned
1111 * Synchronized<vector<int>> svector;
1113 * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
1115 * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
1117 * Refer to folly/docs/Synchronized.md for a detailed explanation and more
1120 #define SYNCHRONIZED(...) \
1121 FOLLY_PUSH_WARNING \
1122 FOLLY_GCC_DISABLE_WARNING(shadow) \
1123 if (bool SYNCHRONIZED_state = false) { \
1125 for (auto SYNCHRONIZED_lockedPtr = \
1126 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \
1127 !SYNCHRONIZED_state; \
1128 SYNCHRONIZED_state = true) \
1129 for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
1130 *SYNCHRONIZED_lockedPtr.operator->(); \
1131 !SYNCHRONIZED_state; \
1132 SYNCHRONIZED_state = true) \
1135 #define TIMED_SYNCHRONIZED(timeout, ...) \
1136 if (bool SYNCHRONIZED_state = false) { \
1138 for (auto SYNCHRONIZED_lockedPtr = \
1139 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \
1140 !SYNCHRONIZED_state; \
1141 SYNCHRONIZED_state = true) \
1142 for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
1143 (!SYNCHRONIZED_lockedPtr \
1145 : SYNCHRONIZED_lockedPtr.operator->()); \
1146 !SYNCHRONIZED_state; \
1147 SYNCHRONIZED_state = true)
1150 * Similar to SYNCHRONIZED, but only uses a read lock.
1152 #define SYNCHRONIZED_CONST(...) \
1154 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
1155 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
1158 * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
1160 #define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
1161 TIMED_SYNCHRONIZED( \
1163 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
1164 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
1167 * Temporarily disables synchronization inside a SYNCHRONIZED block.
1169 * Note: This macro is deprecated, and kind of broken. The input parameter
1170 * does not control what it unlocks--it always unlocks the lock acquired by the
1171 * most recent SYNCHRONIZED scope. If you have two nested SYNCHRONIZED blocks,
1172 * UNSYNCHRONIZED always unlocks the inner-most, even if you pass in the
1173 * variable name used in the outer SYNCHRONIZED block.
1175 * This macro will be removed soon in a subsequent diff.
1177 #define UNSYNCHRONIZED(name) \
1178 for (auto SYNCHRONIZED_state3 = SYNCHRONIZED_lockedPtr.scopedUnlock(); \
1179 !SYNCHRONIZED_state; \
1180 SYNCHRONIZED_state = true) \
1181 for (auto& name = *SYNCHRONIZED_state3.getSynchronized(); \
1182 !SYNCHRONIZED_state; \
1183 SYNCHRONIZED_state = true)
1186 * Synchronizes two Synchronized objects (they may encapsulate
1187 * different data). Synchronization is done in increasing address of
1188 * object order, so there is no deadlock risk.
1190 #define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
1191 if (bool SYNCHRONIZED_state = false) { \
1193 for (auto SYNCHRONIZED_ptrs = acquireLockedPair(e1, e2); \
1194 !SYNCHRONIZED_state; \
1195 SYNCHRONIZED_state = true) \
1196 for (auto& n1 = *SYNCHRONIZED_ptrs.first; !SYNCHRONIZED_state; \
1197 SYNCHRONIZED_state = true) \
1198 for (auto& n2 = *SYNCHRONIZED_ptrs.second; !SYNCHRONIZED_state; \
1199 SYNCHRONIZED_state = true)
1201 } /* namespace folly */