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>
44 * SynchronizedBase is a helper parent class for Synchronized<T>.
46 * It provides wlock() and rlock() methods for shared mutex types,
47 * or lock() methods for purely exclusive mutex types.
49 template <class Subclass, bool is_shared>
50 class SynchronizedBase;
53 * SynchronizedBase specialization for shared mutex types.
55 * This class provides wlock() and rlock() methods for acquiring the lock and
58 template <class Subclass>
59 class SynchronizedBase<Subclass, true> {
61 using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
62 using ConstWLockedPtr =
63 ::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
64 using ConstLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyShared>;
67 * Acquire an exclusive lock, and return a LockedPtr that can be used to
68 * safely access the datum.
70 * LockedPtr offers operator -> and * to provide access to the datum.
71 * The lock will be released when the LockedPtr is destroyed.
74 return LockedPtr(static_cast<Subclass*>(this));
76 ConstWLockedPtr wlock() const {
77 return ConstWLockedPtr(static_cast<const Subclass*>(this));
81 * Acquire a read lock, and return a ConstLockedPtr that can be used to
82 * safely access the datum.
84 ConstLockedPtr rlock() const {
85 return ConstLockedPtr(static_cast<const Subclass*>(this));
89 * Attempts to acquire the lock, or fails if the timeout elapses first.
90 * If acquisition is unsuccessful, the returned LockedPtr will be null.
92 * (Use LockedPtr::isNull() to check for validity.)
94 template <class Rep, class Period>
95 LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) {
96 return LockedPtr(static_cast<Subclass*>(this), timeout);
98 template <class Rep, class Period>
99 ConstWLockedPtr wlock(
100 const std::chrono::duration<Rep, Period>& timeout) const {
101 return ConstWLockedPtr(static_cast<const Subclass*>(this), timeout);
105 * Attempts to acquire the lock, or fails if the timeout elapses first.
106 * If acquisition is unsuccessful, the returned LockedPtr will be null.
108 * (Use LockedPtr::isNull() to check for validity.)
110 template <class Rep, class Period>
111 ConstLockedPtr rlock(
112 const std::chrono::duration<Rep, Period>& timeout) const {
113 return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
118 * SynchronizedBase specialization for non-shared mutex types.
120 * This class provides lock() methods for acquiring the lock and accessing the
123 template <class Subclass>
124 class SynchronizedBase<Subclass, false> {
126 using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
127 using ConstLockedPtr =
128 ::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
131 * Acquire a lock, and return a LockedPtr that can be used to safely access
135 return LockedPtr(static_cast<Subclass*>(this));
139 * Acquire a lock, and return a ConstLockedPtr that can be used to safely
142 ConstLockedPtr lock() const {
143 return ConstLockedPtr(static_cast<const Subclass*>(this));
147 * Attempts to acquire the lock, or fails if the timeout elapses first.
148 * If acquisition is unsuccessful, the returned LockedPtr will be null.
150 template <class Rep, class Period>
151 LockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) {
152 return LockedPtr(static_cast<Subclass*>(this), timeout);
156 * Attempts to acquire the lock, or fails if the timeout elapses first.
157 * If acquisition is unsuccessful, the returned LockedPtr will be null.
159 template <class Rep, class Period>
160 ConstLockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) const {
161 return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
166 * Synchronized<T> encapsulates an object of type T (a "datum") paired
167 * with a mutex. The only way to access the datum is while the mutex
168 * is locked, and Synchronized makes it virtually impossible to do
169 * otherwise. The code that would access the datum in unsafe ways
170 * would look odd and convoluted, thus readily alerting the human
171 * reviewer. In contrast, the code that uses Synchronized<T> correctly
172 * looks simple and intuitive.
174 * The second parameter must be a mutex type. Any mutex type supported by
175 * LockTraits<Mutex> can be used. By default any class with lock() and
176 * unlock() methods will work automatically. LockTraits can be specialized to
177 * teach Synchronized how to use other custom mutex types. See the
178 * documentation in LockTraits.h for additional details.
180 * Supported mutexes that work by default include std::mutex,
181 * std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex,
182 * folly::SharedMutex, folly::RWSpinLock, and folly::SpinLock.
183 * Include LockTraitsBoost.h to get additional LockTraits specializations to
184 * support the following boost mutex types: boost::mutex,
185 * boost::recursive_mutex, boost::shared_mutex, boost::timed_mutex, and
186 * boost::recursive_timed_mutex.
188 template <class T, class Mutex = SharedMutex>
189 struct Synchronized : public SynchronizedBase<
190 Synchronized<T, Mutex>,
191 LockTraits<Mutex>::is_shared> {
194 SynchronizedBase<Synchronized<T, Mutex>, LockTraits<Mutex>::is_shared>;
195 static constexpr bool nxCopyCtor{
196 std::is_nothrow_copy_constructible<T>::value};
197 static constexpr bool nxMoveCtor{
198 std::is_nothrow_move_constructible<T>::value};
201 using LockedPtr = typename Base::LockedPtr;
202 using ConstLockedPtr = typename Base::ConstLockedPtr;
204 using MutexType = Mutex;
207 * Default constructor leaves both members call their own default
210 Synchronized() = default;
213 * Copy constructor copies the data (with locking the source and
214 * all) but does NOT copy the mutex. Doing so would result in
217 Synchronized(const Synchronized& rhs) noexcept(nxCopyCtor)
218 : Synchronized(rhs, rhs.contextualRLock()) {}
221 * Move constructor moves the data (with locking the source and all)
222 * but does not move the mutex.
224 Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor)
225 : Synchronized(std::move(rhs), rhs.contextualLock()) {}
228 * Constructor taking a datum as argument copies it. There is no
229 * need to lock the constructing object.
231 explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
234 * Constructor taking a datum rvalue as argument moves it. Again,
235 * there is no need to lock the constructing object.
237 explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
238 : datum_(std::move(rhs)) {}
241 * Lets you construct non-movable types in-place. Use the constexpr
242 * instance `construct_in_place` as the first argument.
244 template <typename... Args>
245 explicit Synchronized(construct_in_place_t, Args&&... args)
246 : datum_(std::forward<Args>(args)...) {}
249 * The canonical assignment operator only assigns the data, NOT the
250 * mutex. It locks the two objects in ascending order of their
253 Synchronized& operator=(const Synchronized& rhs) {
255 // Self-assignment, pass.
256 } else if (this < &rhs) {
257 auto guard1 = operator->();
258 auto guard2 = rhs.operator->();
261 auto guard1 = rhs.operator->();
262 auto guard2 = operator->();
269 * Move assignment operator, only assigns the data, NOT the
270 * mutex. It locks the two objects in ascending order of their
273 Synchronized& operator=(Synchronized&& rhs) {
275 // Self-assignment, pass.
276 } else if (this < &rhs) {
277 auto guard1 = operator->();
278 auto guard2 = rhs.operator->();
279 datum_ = std::move(rhs.datum_);
281 auto guard1 = rhs.operator->();
282 auto guard2 = operator->();
283 datum_ = std::move(rhs.datum_);
289 * Lock object, assign datum.
291 Synchronized& operator=(const T& rhs) {
292 auto guard = operator->();
298 * Lock object, move-assign datum.
300 Synchronized& operator=(T&& rhs) {
301 auto guard = operator->();
302 datum_ = std::move(rhs);
307 * Acquire an appropriate lock based on the context.
309 * If the mutex is a shared mutex, and the Synchronized instance is const,
310 * this acquires a shared lock. Otherwise this acquires an exclusive lock.
312 * In general, prefer using the explicit rlock() and wlock() methods
313 * for read-write locks, and lock() for purely exclusive locks.
315 * contextualLock() is primarily intended for use in other template functions
316 * that do not necessarily know the lock type.
318 LockedPtr contextualLock() {
319 return LockedPtr(this);
321 ConstLockedPtr contextualLock() const {
322 return ConstLockedPtr(this);
324 template <class Rep, class Period>
325 LockedPtr contextualLock(const std::chrono::duration<Rep, Period>& timeout) {
326 return LockedPtr(this, timeout);
328 template <class Rep, class Period>
329 ConstLockedPtr contextualLock(
330 const std::chrono::duration<Rep, Period>& timeout) const {
331 return ConstLockedPtr(this, timeout);
334 * contextualRLock() acquires a read lock if the mutex type is shared,
335 * or a regular exclusive lock for non-shared mutex types.
337 * contextualRLock() when you know that you prefer a read lock (if
338 * available), even if the Synchronized<T> object itself is non-const.
340 ConstLockedPtr contextualRLock() const {
341 return ConstLockedPtr(this);
343 template <class Rep, class Period>
344 ConstLockedPtr contextualRLock(
345 const std::chrono::duration<Rep, Period>& timeout) const {
346 return ConstLockedPtr(this, timeout);
350 * This accessor offers a LockedPtr. In turn, LockedPtr offers
351 * operator-> returning a pointer to T. The operator-> keeps
352 * expanding until it reaches a pointer, so syncobj->foo() will lock
353 * the object and call foo() against it.
355 * NOTE: This API is planned to be deprecated in an upcoming diff.
356 * Prefer using lock(), wlock(), or rlock() instead.
358 LockedPtr operator->() {
359 return LockedPtr(this);
363 * Obtain a ConstLockedPtr.
365 * NOTE: This API is planned to be deprecated in an upcoming diff.
366 * Prefer using lock(), wlock(), or rlock() instead.
368 ConstLockedPtr operator->() const {
369 return ConstLockedPtr(this);
373 * Attempts to acquire for a given number of milliseconds. If
374 * acquisition is unsuccessful, the returned LockedPtr is NULL.
376 * NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead.
377 * In the future it will be marked with a deprecation attribute to emit
378 * build-time warnings, and then it will be removed entirely.
380 LockedPtr timedAcquire(unsigned int milliseconds) {
381 return LockedPtr(this, std::chrono::milliseconds(milliseconds));
385 * Attempts to acquire for a given number of milliseconds. If
386 * acquisition is unsuccessful, the returned ConstLockedPtr is NULL.
388 * NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead.
389 * In the future it will be marked with a deprecation attribute to emit
390 * build-time warnings, and then it will be removed entirely.
392 ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
393 return ConstLockedPtr(this, std::chrono::milliseconds(milliseconds));
397 * Sometimes, although you have a mutable object, you only want to
398 * call a const method against it. The most efficient way to achieve
399 * that is by using a read lock. You get to do so by using
400 * obj.asConst()->method() instead of obj->method().
402 * NOTE: This API is planned to be deprecated in an upcoming diff.
403 * Use rlock() instead.
405 const Synchronized& asConst() const {
410 * Swaps with another Synchronized. Protected against
411 * self-swap. Only data is swapped. Locks are acquired in increasing
414 void swap(Synchronized& rhs) {
419 return rhs.swap(*this);
421 auto guard1 = operator->();
422 auto guard2 = rhs.operator->();
425 swap(datum_, rhs.datum_);
429 * Swap with another datum. Recommended because it keeps the mutex
433 LockedPtr guard(this);
440 * Copies datum to a given target.
442 void copy(T* target) const {
443 ConstLockedPtr guard(this);
448 * Returns a fresh copy of the datum.
451 ConstLockedPtr guard(this);
456 template <class LockedType, class MutexType, class LockPolicy>
457 friend class folly::LockedPtrBase;
458 template <class LockedType, class LockPolicy>
459 friend class folly::LockedPtr;
462 * Helper constructors to enable Synchronized for
463 * non-default constructible types T.
464 * Guards are created in actual public constructors and are alive
465 * for the time required to construct the object
468 const Synchronized& rhs,
469 const ConstLockedPtr& /*guard*/) noexcept(nxCopyCtor)
470 : datum_(rhs.datum_) {}
472 Synchronized(Synchronized&& rhs, const LockedPtr& /*guard*/) noexcept(
474 : datum_(std::move(rhs.datum_)) {}
476 // Synchronized data members
478 mutable Mutex mutex_;
481 template <class SynchronizedType, class LockPolicy>
482 class ScopedUnlocker;
485 * A helper base class for implementing LockedPtr.
487 * The main reason for having this as a separate class is so we can specialize
488 * it for std::mutex, so we can expose a std::unique_lock to the caller
489 * when std::mutex is being used. This allows callers to use a
490 * std::condition_variable with the mutex from a Synchronized<T, std::mutex>.
492 * We don't use std::unique_lock with other Mutex types since it makes the
493 * LockedPtr class slightly larger, and it makes the logic to support
494 * ScopedUnlocker slightly more complicated. std::mutex is the only one that
495 * really seems to benefit from the unique_lock. std::condition_variable
496 * itself only supports std::unique_lock<std::mutex>, so there doesn't seem to
497 * be any real benefit to exposing the unique_lock with other mutex types.
499 * Note that the SynchronizedType template parameter may or may not be const
502 template <class SynchronizedType, class Mutex, class LockPolicy>
503 class LockedPtrBase {
505 using MutexType = Mutex;
506 friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
509 * Destructor releases.
513 LockPolicy::unlock(parent_->mutex_);
519 explicit LockedPtrBase(SynchronizedType* parent) : parent_(parent) {
520 LockPolicy::lock(parent_->mutex_);
522 template <class Rep, class Period>
524 SynchronizedType* parent,
525 const std::chrono::duration<Rep, Period>& timeout) {
526 if (LockPolicy::try_lock_for(parent->mutex_, timeout)) {
527 this->parent_ = parent;
530 LockedPtrBase(LockedPtrBase&& rhs) noexcept : parent_(rhs.parent_) {
531 rhs.parent_ = nullptr;
533 LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
535 LockPolicy::unlock(parent_->mutex_);
538 parent_ = rhs.parent_;
539 rhs.parent_ = nullptr;
543 using UnlockerData = SynchronizedType*;
546 * Get a pointer to the Synchronized object from the UnlockerData.
548 * In the generic case UnlockerData is just the Synchronized pointer,
549 * so we return it as is. (This function is more interesting in the
550 * std::mutex specialization below.)
552 static SynchronizedType* getSynchronized(UnlockerData data) {
556 UnlockerData releaseLock() {
557 auto current = parent_;
559 LockPolicy::unlock(current->mutex_);
562 void reacquireLock(UnlockerData&& data) {
563 DCHECK(parent_ == nullptr);
565 LockPolicy::lock(parent_->mutex_);
568 SynchronizedType* parent_ = nullptr;
572 * LockedPtrBase specialization for use with std::mutex.
574 * When std::mutex is used we use a std::unique_lock to hold the mutex.
575 * This makes it possible to use std::condition_variable with a
576 * Synchronized<T, std::mutex>.
578 template <class SynchronizedType, class LockPolicy>
579 class LockedPtrBase<SynchronizedType, std::mutex, LockPolicy> {
581 using MutexType = std::mutex;
582 friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
585 * Destructor releases.
588 // The std::unique_lock will automatically release the lock when it is
589 // destroyed, so we don't need to do anything extra here.
592 LockedPtrBase(LockedPtrBase&& rhs) noexcept
593 : lock_(std::move(rhs.lock_)), parent_(rhs.parent_) {
594 rhs.parent_ = nullptr;
596 LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
597 lock_ = std::move(rhs.lock_);
598 parent_ = rhs.parent_;
599 rhs.parent_ = nullptr;
604 * Get a reference to the std::unique_lock.
606 * This is provided so that callers can use Synchronized<T, std::mutex>
607 * with a std::condition_variable.
609 * While this API could be used to bypass the normal Synchronized APIs and
610 * manually interact with the underlying unique_lock, this is strongly
613 std::unique_lock<std::mutex>& getUniqueLock() {
619 explicit LockedPtrBase(SynchronizedType* parent)
620 : lock_(parent->mutex_), parent_(parent) {}
623 std::pair<std::unique_lock<std::mutex>, SynchronizedType*>;
625 static SynchronizedType* getSynchronized(const UnlockerData& data) {
629 UnlockerData releaseLock() {
630 UnlockerData data(std::move(lock_), parent_);
635 void reacquireLock(UnlockerData&& data) {
636 lock_ = std::move(data.first);
638 parent_ = data.second;
641 // The specialization for std::mutex does have to store slightly more
642 // state than the default implementation.
643 std::unique_lock<std::mutex> lock_;
644 SynchronizedType* parent_ = nullptr;
648 * This class temporarily unlocks a LockedPtr in a scoped manner.
650 template <class SynchronizedType, class LockPolicy>
651 class ScopedUnlocker {
653 explicit ScopedUnlocker(LockedPtr<SynchronizedType, LockPolicy>* p)
654 : ptr_(p), data_(ptr_->releaseLock()) {}
655 ScopedUnlocker(const ScopedUnlocker&) = delete;
656 ScopedUnlocker& operator=(const ScopedUnlocker&) = delete;
657 ScopedUnlocker(ScopedUnlocker&& other) noexcept
658 : ptr_(other.ptr_), data_(std::move(other.data_)) {
659 other.ptr_ = nullptr;
661 ScopedUnlocker& operator=(ScopedUnlocker&& other) = delete;
665 ptr_->reacquireLock(std::move(data_));
670 * Return a pointer to the Synchronized object used by this ScopedUnlocker.
672 SynchronizedType* getSynchronized() const {
673 return LockedPtr<SynchronizedType, LockPolicy>::getSynchronized(data_);
677 using Data = typename LockedPtr<SynchronizedType, LockPolicy>::UnlockerData;
678 LockedPtr<SynchronizedType, LockPolicy>* ptr_{nullptr};
683 * A LockedPtr keeps a Synchronized<T> object locked for the duration of
684 * LockedPtr's existence.
686 * It provides access the datum's members directly by using operator->() and
689 * The LockPolicy parameter controls whether or not the lock is acquired in
690 * exclusive or shared mode.
692 template <class SynchronizedType, class LockPolicy>
693 class LockedPtr : public LockedPtrBase<
695 typename SynchronizedType::MutexType,
698 using Base = LockedPtrBase<
700 typename SynchronizedType::MutexType,
702 using UnlockerData = typename Base::UnlockerData;
703 // CDataType is the DataType with the appropriate const-qualification
704 using CDataType = typename std::conditional<
705 std::is_const<SynchronizedType>::value,
706 typename SynchronizedType::DataType const,
707 typename SynchronizedType::DataType>::type;
710 using DataType = typename SynchronizedType::DataType;
711 using MutexType = typename SynchronizedType::MutexType;
712 using Synchronized = typename std::remove_const<SynchronizedType>::type;
713 friend class ScopedUnlocker<SynchronizedType, LockPolicy>;
716 * Creates an uninitialized LockedPtr.
718 * Dereferencing an uninitialized LockedPtr is not allowed.
723 * Takes a Synchronized<T> and locks it.
725 explicit LockedPtr(SynchronizedType* parent) : Base(parent) {}
728 * Takes a Synchronized<T> and attempts to lock it, within the specified
731 * Blocks until the lock is acquired or until the specified timeout expires.
732 * If the timeout expired without acquiring the lock, the LockedPtr will be
733 * null, and LockedPtr::isNull() will return true.
735 template <class Rep, class Period>
737 SynchronizedType* parent,
738 const std::chrono::duration<Rep, Period>& timeout)
739 : Base(parent, timeout) {}
744 LockedPtr(LockedPtr&& rhs) noexcept = default;
747 * Move assignment operator.
749 LockedPtr& operator=(LockedPtr&& rhs) noexcept = default;
752 * Copy constructor and assignment operator are deleted.
754 LockedPtr(const LockedPtr& rhs) = delete;
755 LockedPtr& operator=(const LockedPtr& rhs) = delete;
758 * Destructor releases.
763 * Check if this LockedPtr is uninitialized, or points to valid locked data.
765 * This method can be used to check if a timed-acquire operation succeeded.
766 * If an acquire operation times out it will result in a null LockedPtr.
768 * A LockedPtr is always either null, or holds a lock to valid data.
769 * Methods such as scopedUnlock() reset the LockedPtr to null for the
770 * duration of the unlock.
772 bool isNull() const {
773 return this->parent_ == nullptr;
777 * Explicit boolean conversion.
781 explicit operator bool() const {
782 return this->parent_ != nullptr;
786 * Access the locked data.
788 * This method should only be used if the LockedPtr is valid.
790 CDataType* operator->() const {
791 return &this->parent_->datum_;
795 * Access the locked data.
797 * This method should only be used if the LockedPtr is valid.
799 CDataType& operator*() const {
800 return this->parent_->datum_;
804 * Temporarily unlock the LockedPtr, and reset it to null.
806 * Returns an helper object that will re-lock and restore the LockedPtr when
807 * the helper is destroyed. The LockedPtr may not be dereferenced for as
808 * long as this helper object exists.
810 ScopedUnlocker<SynchronizedType, LockPolicy> scopedUnlock() {
811 return ScopedUnlocker<SynchronizedType, LockPolicy>(this);
817 * A helper alias to select a ConstLockedPtr if the template parameter is a
818 * const Synchronized<T>, or a LockedPtr if the parameter is not const.
820 template <class SynchronizedType>
821 using LockedPtrType = typename std::conditional<
822 std::is_const<SynchronizedType>::value,
823 typename SynchronizedType::ConstLockedPtr,
824 typename SynchronizedType::LockedPtr>::type;
828 * Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
831 * The locks are acquired in order from lowest address to highest address.
832 * (Note that this is not necessarily the same algorithm used by std::lock().)
834 * For parameters that are const and support shared locks, a read lock is
835 * acquired. Otherwise an exclusive lock is acquired.
837 * TODO: Extend acquireLocked() with variadic template versions that
838 * allow for more than 2 Synchronized arguments. (I haven't given too much
839 * thought about how to implement this. It seems like it would be rather
840 * complicated, but I think it should be possible.)
842 template <class Sync1, class Sync2>
843 std::tuple<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
844 acquireLocked(Sync1& l1, Sync2& l2) {
845 if (static_cast<const void*>(&l1) < static_cast<const void*>(&l2)) {
846 auto p1 = l1.contextualLock();
847 auto p2 = l2.contextualLock();
848 return std::make_tuple(std::move(p1), std::move(p2));
850 auto p2 = l2.contextualLock();
851 auto p1 = l1.contextualLock();
852 return std::make_tuple(std::move(p1), std::move(p2));
857 * A version of acquireLocked() that returns a std::pair rather than a
858 * std::tuple, which is easier to use in many places.
860 template <class Sync1, class Sync2>
861 std::pair<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
862 acquireLockedPair(Sync1& l1, Sync2& l2) {
863 auto lockedPtrs = acquireLocked(l1, l2);
864 return {std::move(std::get<0>(lockedPtrs)),
865 std::move(std::get<1>(lockedPtrs))};
868 /************************************************************************
869 * NOTE: All APIs below this line will be deprecated in upcoming diffs.
870 ************************************************************************/
872 // Non-member swap primitive
873 template <class T, class M>
874 void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
879 * SYNCHRONIZED is the main facility that makes Synchronized<T>
880 * helpful. It is a pseudo-statement that introduces a scope where the
881 * object is locked. Inside that scope you get to access the unadorned
886 * Synchronized<vector<int>> svector;
888 * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
890 * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
892 * Refer to folly/docs/Synchronized.md for a detailed explanation and more
895 #define SYNCHRONIZED(...) \
897 FOLLY_GCC_DISABLE_WARNING(shadow) \
898 if (bool SYNCHRONIZED_state = false) { \
900 for (auto SYNCHRONIZED_lockedPtr = \
901 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \
902 !SYNCHRONIZED_state; \
903 SYNCHRONIZED_state = true) \
904 for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
905 *SYNCHRONIZED_lockedPtr.operator->(); \
906 !SYNCHRONIZED_state; \
907 SYNCHRONIZED_state = true) \
910 #define TIMED_SYNCHRONIZED(timeout, ...) \
911 if (bool SYNCHRONIZED_state = false) { \
913 for (auto SYNCHRONIZED_lockedPtr = \
914 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \
915 !SYNCHRONIZED_state; \
916 SYNCHRONIZED_state = true) \
917 for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
918 (!SYNCHRONIZED_lockedPtr \
920 : SYNCHRONIZED_lockedPtr.operator->()); \
921 !SYNCHRONIZED_state; \
922 SYNCHRONIZED_state = true)
925 * Similar to SYNCHRONIZED, but only uses a read lock.
927 #define SYNCHRONIZED_CONST(...) \
929 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
930 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
933 * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
935 #define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
936 TIMED_SYNCHRONIZED( \
938 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
939 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
942 * Temporarily disables synchronization inside a SYNCHRONIZED block.
944 * Note: This macro is deprecated, and kind of broken. The input parameter
945 * does not control what it unlocks--it always unlocks the lock acquired by the
946 * most recent SYNCHRONIZED scope. If you have two nested SYNCHRONIZED blocks,
947 * UNSYNCHRONIZED always unlocks the inner-most, even if you pass in the
948 * variable name used in the outer SYNCHRONIZED block.
950 * This macro will be removed soon in a subsequent diff.
952 #define UNSYNCHRONIZED(name) \
953 for (auto SYNCHRONIZED_state3 = SYNCHRONIZED_lockedPtr.scopedUnlock(); \
954 !SYNCHRONIZED_state; \
955 SYNCHRONIZED_state = true) \
956 for (auto& name = *SYNCHRONIZED_state3.getSynchronized(); \
957 !SYNCHRONIZED_state; \
958 SYNCHRONIZED_state = true)
961 * Synchronizes two Synchronized objects (they may encapsulate
962 * different data). Synchronization is done in increasing address of
963 * object order, so there is no deadlock risk.
965 #define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
966 if (bool SYNCHRONIZED_state = false) { \
968 for (auto SYNCHRONIZED_ptrs = acquireLockedPair(e1, e2); \
969 !SYNCHRONIZED_state; \
970 SYNCHRONIZED_state = true) \
971 for (auto& n1 = *SYNCHRONIZED_ptrs.first; !SYNCHRONIZED_state; \
972 SYNCHRONIZED_state = true) \
973 for (auto& n2 = *SYNCHRONIZED_ptrs.second; !SYNCHRONIZED_state; \
974 SYNCHRONIZED_state = true)
976 } /* namespace folly */