2 * Copyright 2015 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 * @author: Andrei Alexandrescu (andrei.alexandrescu@fb.com)
24 #ifndef SYNCHRONIZED_H_
25 #define SYNCHRONIZED_H_
27 #include <type_traits>
29 #include <boost/thread.hpp>
30 #include <folly/Preprocessor.h>
31 #include <folly/Traits.h>
36 enum InternalDoNotUse {};
39 * Free function adaptors for std:: and boost::
42 // Android, OSX, and Cygwin don't have timed mutexes
43 #if defined(ANDROID) || defined(__ANDROID__) || \
44 defined(__APPLE__) || defined(__CYGWIN__)
45 # define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES 0
47 # define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES 1
51 * Yields true iff T has .lock() and .unlock() member functions. This
52 * is done by simply enumerating the mutexes with this interface in
56 struct HasLockUnlock {
57 enum { value = IsOneOf<T,
58 std::mutex, std::recursive_mutex,
59 boost::mutex, boost::recursive_mutex, boost::shared_mutex
60 #if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
61 ,std::timed_mutex, std::recursive_timed_mutex,
62 boost::timed_mutex, boost::recursive_timed_mutex
68 * Acquires a mutex for reading by calling .lock(). The exception is
69 * boost::shared_mutex, which has a special read-lock primitive called
73 typename std::enable_if<
74 HasLockUnlock<T>::value && !std::is_same<T, boost::shared_mutex>::value>::type
75 acquireRead(T& mutex) {
80 * Special case for boost::shared_mutex.
83 typename std::enable_if<std::is_same<T, boost::shared_mutex>::value>::type
84 acquireRead(T& mutex) {
89 * Acquires a mutex for reading with timeout by calling .timed_lock(). This
90 * applies to three of the boost mutex classes as enumerated below.
93 typename std::enable_if<std::is_same<T, boost::shared_mutex>::value, bool>::type
95 unsigned int milliseconds) {
96 return mutex.timed_lock_shared(boost::posix_time::milliseconds(milliseconds));
100 * Acquires a mutex for reading and writing by calling .lock().
103 typename std::enable_if<HasLockUnlock<T>::value>::type
104 acquireReadWrite(T& mutex) {
108 #if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
110 * Acquires a mutex for reading and writing with timeout by calling
111 * .try_lock_for(). This applies to two of the std mutex classes as
115 typename std::enable_if<
116 IsOneOf<T, std::timed_mutex, std::recursive_timed_mutex>::value, bool>::type
117 acquireReadWrite(T& mutex,
118 unsigned int milliseconds) {
119 // work around try_lock_for bug in some gcc versions, see
120 // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54562
121 return mutex.try_lock()
122 || (milliseconds > 0 &&
123 mutex.try_lock_until(std::chrono::system_clock::now() +
124 std::chrono::milliseconds(milliseconds)));
128 * Acquires a mutex for reading and writing with timeout by calling
129 * .timed_lock(). This applies to three of the boost mutex classes as
133 typename std::enable_if<
134 IsOneOf<T, boost::shared_mutex, boost::timed_mutex,
135 boost::recursive_timed_mutex>::value, bool>::type
136 acquireReadWrite(T& mutex,
137 unsigned int milliseconds) {
138 return mutex.timed_lock(boost::posix_time::milliseconds(milliseconds));
140 #endif // FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
143 * Releases a mutex previously acquired for reading by calling
144 * .unlock(). The exception is boost::shared_mutex, which has a
145 * special primitive called .unlock_shared().
148 typename std::enable_if<
149 HasLockUnlock<T>::value && !std::is_same<T, boost::shared_mutex>::value>::type
150 releaseRead(T& mutex) {
155 * Special case for boost::shared_mutex.
158 typename std::enable_if<std::is_same<T, boost::shared_mutex>::value>::type
159 releaseRead(T& mutex) {
160 mutex.unlock_shared();
164 * Releases a mutex previously acquired for reading-writing by calling
168 typename std::enable_if<HasLockUnlock<T>::value>::type
169 releaseReadWrite(T& mutex) {
173 } // namespace detail
176 * Synchronized<T> encapsulates an object of type T (a "datum") paired
177 * with a mutex. The only way to access the datum is while the mutex
178 * is locked, and Synchronized makes it virtually impossible to do
179 * otherwise. The code that would access the datum in unsafe ways
180 * would look odd and convoluted, thus readily alerting the human
181 * reviewer. In contrast, the code that uses Synchronized<T> correctly
182 * looks simple and intuitive.
184 * The second parameter must be a mutex type. Supported mutexes are
185 * std::mutex, std::recursive_mutex, std::timed_mutex,
186 * std::recursive_timed_mutex, boost::mutex, boost::recursive_mutex,
187 * boost::shared_mutex, boost::timed_mutex,
188 * boost::recursive_timed_mutex, and the folly/RWSpinLock.h
191 * You may define Synchronized support by defining 4-6 primitives in
192 * the same namespace as the mutex class (found via ADL). The
193 * primitives are: acquireRead, acquireReadWrite, releaseRead, and
194 * releaseReadWrite. Two optional primitives for timout operations are
195 * overloads of acquireRead and acquireReadWrite. For signatures,
196 * refer to the namespace detail below, which implements the
197 * primitives for mutexes in std and boost.
199 template <class T, class Mutex = boost::shared_mutex>
200 struct Synchronized {
202 * Default constructor leaves both members call their own default
205 Synchronized() = default;
208 static constexpr bool nxCopyCtor{
209 std::is_nothrow_copy_constructible<T>::value};
210 static constexpr bool nxMoveCtor{
211 std::is_nothrow_move_constructible<T>::value};
214 * Helper constructors to enable Synchronized for
215 * non-default constructible types T.
216 * Guards are created in actual public constructors and are alive
217 * for the time required to construct the object
219 template <typename Guard>
220 Synchronized(const Synchronized& rhs,
221 const Guard& /*guard*/) noexcept(nxCopyCtor)
222 : datum_(rhs.datum_) {}
224 template <typename Guard>
225 Synchronized(Synchronized&& rhs, const Guard& /*guard*/) noexcept(nxMoveCtor)
226 : datum_(std::move(rhs.datum_)) {}
230 * Copy constructor copies the data (with locking the source and
231 * all) but does NOT copy the mutex. Doing so would result in
234 Synchronized(const Synchronized& rhs) noexcept(nxCopyCtor)
235 : Synchronized(rhs, rhs.operator->()) {}
238 * Move constructor moves the data (with locking the source and all)
239 * but does not move the mutex.
241 Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor)
242 : Synchronized(std::move(rhs), rhs.operator->()) {}
245 * Constructor taking a datum as argument copies it. There is no
246 * need to lock the constructing object.
248 explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
251 * Constructor taking a datum rvalue as argument moves it. Again,
252 * there is no need to lock the constructing object.
254 explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
255 : datum_(std::move(rhs)) {}
258 * The canonical assignment operator only assigns the data, NOT the
259 * mutex. It locks the two objects in ascending order of their
262 Synchronized& operator=(const Synchronized& rhs) {
264 // Self-assignment, pass.
265 } else if (this < &rhs) {
266 auto guard1 = operator->();
267 auto guard2 = rhs.operator->();
270 auto guard1 = rhs.operator->();
271 auto guard2 = operator->();
278 * Move assignment operator, only assigns the data, NOT the
279 * mutex. It locks the two objects in ascending order of their
282 Synchronized& operator=(Synchronized&& rhs) {
284 // Self-assignment, pass.
285 } else if (this < &rhs) {
286 auto guard1 = operator->();
287 auto guard2 = rhs.operator->();
288 datum_ = std::move(rhs.datum_);
290 auto guard1 = rhs.operator->();
291 auto guard2 = operator->();
292 datum_ = std::move(rhs.datum_);
298 * Lock object, assign datum.
300 Synchronized& operator=(const T& rhs) {
301 auto guard = operator->();
307 * Lock object, move-assign datum.
309 Synchronized& operator=(T&& rhs) {
310 auto guard = operator->();
311 datum_ = std::move(rhs);
316 * A LockedPtr lp keeps a modifiable (i.e. non-const)
317 * Synchronized<T> object locked for the duration of lp's
318 * existence. Because of this, you get to access the datum's methods
319 * directly by using lp->fun().
323 * Found no reason to leave this hanging.
325 LockedPtr() = delete;
328 * Takes a Synchronized and locks it.
330 explicit LockedPtr(Synchronized* parent) : parent_(parent) {
335 * Takes a Synchronized and attempts to lock it for some
336 * milliseconds. If not, the LockedPtr will be subsequently null.
338 LockedPtr(Synchronized* parent, unsigned int milliseconds) {
339 using namespace detail;
340 if (acquireReadWrite(parent->mutex_, milliseconds)) {
344 // Could not acquire the resource, pointer is null
349 * This is used ONLY inside SYNCHRONIZED_DUAL. It initializes
350 * everything properly, but does not lock the parent because it
351 * "knows" someone else will lock it. Please do not use.
353 LockedPtr(Synchronized* parent, detail::InternalDoNotUse)
358 * Copy ctor adds one lock.
360 LockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
365 * Assigning from another LockedPtr results in freeing the former
366 * lock and acquiring the new one. The method works with
367 * self-assignment (does nothing).
369 LockedPtr& operator=(const LockedPtr& rhs) {
370 if (parent_ != rhs.parent_) {
371 if (parent_) parent_->mutex_.unlock();
372 parent_ = rhs.parent_;
379 * Destructor releases.
382 using namespace detail;
383 if (parent_) releaseReadWrite(parent_->mutex_);
387 * Safe to access the data. Don't save the obtained pointer by
388 * invoking lp.operator->() by hand. Also, if the method returns a
389 * handle stored inside the datum, don't use this idiom - use
390 * SYNCHRONIZED below.
393 return parent_ ? &parent_->datum_ : nullptr;
397 * This class temporarily unlocks a LockedPtr in a scoped
398 * manner. It is used inside of the UNSYNCHRONIZED macro.
400 struct Unsynchronizer {
401 explicit Unsynchronizer(LockedPtr* p) : parent_(p) {
402 using namespace detail;
403 releaseReadWrite(parent_->parent_->mutex_);
405 Unsynchronizer(const Unsynchronizer&) = delete;
406 Unsynchronizer& operator=(const Unsynchronizer&) = delete;
410 LockedPtr* operator->() const {
416 friend struct Unsynchronizer;
417 Unsynchronizer typeHackDoNotUse();
419 template <class P1, class P2>
420 friend void lockInOrder(P1& p1, P2& p2);
424 using namespace detail;
425 if (parent_) acquireReadWrite(parent_->mutex_);
428 // This is the entire state of LockedPtr.
429 Synchronized* parent_;
433 * ConstLockedPtr does exactly what LockedPtr does, but for const
434 * Synchronized objects. Of interest is that ConstLockedPtr only
435 * uses a read lock, which is faster but more restrictive - you only
436 * get to call const methods of the datum.
438 * Much of the code between LockedPtr and
439 * ConstLockedPtr is identical and could be factor out, but there
440 * are enough nagging little differences to not justify the trouble.
442 struct ConstLockedPtr {
443 ConstLockedPtr() = delete;
444 explicit ConstLockedPtr(const Synchronized* parent) : parent_(parent) {
447 ConstLockedPtr(const Synchronized* parent, detail::InternalDoNotUse)
450 ConstLockedPtr(const ConstLockedPtr& rhs) : parent_(rhs.parent_) {
453 explicit ConstLockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
456 ConstLockedPtr(const Synchronized* parent, unsigned int milliseconds) {
457 if (parent->mutex_.timed_lock_shared(
458 boost::posix_time::milliseconds(milliseconds))) {
462 // Could not acquire the resource, pointer is null
466 ConstLockedPtr& operator=(const ConstLockedPtr& rhs) {
467 if (parent_ != rhs.parent_) {
468 if (parent_) parent_->mutex_.unlock_shared();
469 parent_ = rhs.parent_;
474 using namespace detail;
475 if (parent_) releaseRead(parent_->mutex_);
478 const T* operator->() const {
479 return parent_ ? &parent_->datum_ : nullptr;
482 struct Unsynchronizer {
483 explicit Unsynchronizer(ConstLockedPtr* p) : parent_(p) {
484 using namespace detail;
485 releaseRead(parent_->parent_->mutex_);
487 Unsynchronizer(const Unsynchronizer&) = delete;
488 Unsynchronizer& operator=(const Unsynchronizer&) = delete;
490 using namespace detail;
491 acquireRead(parent_->parent_->mutex_);
493 ConstLockedPtr* operator->() const {
497 ConstLockedPtr* parent_;
499 friend struct Unsynchronizer;
500 Unsynchronizer typeHackDoNotUse();
502 template <class P1, class P2>
503 friend void lockInOrder(P1& p1, P2& p2);
507 using namespace detail;
508 if (parent_) acquireRead(parent_->mutex_);
511 const Synchronized* parent_;
515 * This accessor offers a LockedPtr. In turn. LockedPtr offers
516 * operator-> returning a pointer to T. The operator-> keeps
517 * expanding until it reaches a pointer, so syncobj->foo() will lock
518 * the object and call foo() against it.
520 LockedPtr operator->() {
521 return LockedPtr(this);
525 * Same, for constant objects. You will be able to invoke only const
528 ConstLockedPtr operator->() const {
529 return ConstLockedPtr(this);
533 * Attempts to acquire for a given number of milliseconds. If
534 * acquisition is unsuccessful, the returned LockedPtr is NULL.
536 LockedPtr timedAcquire(unsigned int milliseconds) {
537 return LockedPtr(this, milliseconds);
541 * As above, for a constant object.
543 ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
544 return ConstLockedPtr(this, milliseconds);
548 * Used by SYNCHRONIZED_DUAL.
550 LockedPtr internalDoNotUse() {
551 return LockedPtr(this, detail::InternalDoNotUse());
557 ConstLockedPtr internalDoNotUse() const {
558 return ConstLockedPtr(this, detail::InternalDoNotUse());
562 * Sometimes, although you have a mutable object, you only want to
563 * call a const method against it. The most efficient way to achieve
564 * that is by using a read lock. You get to do so by using
565 * obj.asConst()->method() instead of obj->method().
567 const Synchronized& asConst() const {
572 * Swaps with another Synchronized. Protected against
573 * self-swap. Only data is swapped. Locks are acquired in increasing
576 void swap(Synchronized& rhs) {
581 return rhs.swap(*this);
583 auto guard1 = operator->();
584 auto guard2 = rhs.operator->();
587 swap(datum_, rhs.datum_);
591 * Swap with another datum. Recommended because it keeps the mutex
595 LockedPtr guard = operator->();
602 * Copies datum to a given target.
604 void copy(T* target) const {
605 ConstLockedPtr guard = operator->();
610 * Returns a fresh copy of the datum.
613 ConstLockedPtr guard = operator->();
619 mutable Mutex mutex_;
622 // Non-member swap primitive
623 template <class T, class M>
624 void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
629 * SYNCHRONIZED is the main facility that makes Synchronized<T>
630 * helpful. It is a pseudo-statement that introduces a scope where the
631 * object is locked. Inside that scope you get to access the unadorned
636 * Synchronized<vector<int>> svector;
638 * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
640 * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
642 * Refer to folly/docs/Synchronized.md for a detailed explanation and more
645 #define SYNCHRONIZED(...) \
646 if (bool SYNCHRONIZED_state = false) {} else \
647 for (auto SYNCHRONIZED_lockedPtr = \
648 (FB_ARG_2_OR_1(__VA_ARGS__)).operator->(); \
649 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
650 for (auto& FB_ARG_1(__VA_ARGS__) = \
651 *SYNCHRONIZED_lockedPtr.operator->(); \
652 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
654 #define TIMED_SYNCHRONIZED(timeout, ...) \
655 if (bool SYNCHRONIZED_state = false) {} else \
656 for (auto SYNCHRONIZED_lockedPtr = \
657 (FB_ARG_2_OR_1(__VA_ARGS__)).timedAcquire(timeout); \
658 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
659 for (auto FB_ARG_1(__VA_ARGS__) = \
660 SYNCHRONIZED_lockedPtr.operator->(); \
661 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
664 * Similar to SYNCHRONIZED, but only uses a read lock.
666 #define SYNCHRONIZED_CONST(...) \
667 SYNCHRONIZED(FB_ARG_1(__VA_ARGS__), \
668 (FB_ARG_2_OR_1(__VA_ARGS__)).asConst())
671 * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
673 #define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
674 TIMED_SYNCHRONIZED(timeout, FB_ARG_1(__VA_ARGS__), \
675 (FB_ARG_2_OR_1(__VA_ARGS__)).asConst())
678 * Temporarily disables synchronization inside a SYNCHRONIZED block.
680 #define UNSYNCHRONIZED(name) \
681 for (decltype(SYNCHRONIZED_lockedPtr.typeHackDoNotUse()) \
682 SYNCHRONIZED_state3(&SYNCHRONIZED_lockedPtr); \
683 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
684 for (auto name = *SYNCHRONIZED_state3.operator->(); \
685 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
688 * Locks two objects in increasing order of their addresses.
690 template <class P1, class P2>
691 void lockInOrder(P1& p1, P2& p2) {
692 if (static_cast<const void*>(p1.operator->()) >
693 static_cast<const void*>(p2.operator->())) {
703 * Synchronizes two Synchronized objects (they may encapsulate
704 * different data). Synchronization is done in increasing address of
705 * object order, so there is no deadlock risk.
707 #define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
708 if (bool SYNCHRONIZED_state = false) {} else \
709 for (auto SYNCHRONIZED_lp1 = (e1).internalDoNotUse(); \
710 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
711 for (auto& n1 = *SYNCHRONIZED_lp1.operator->(); \
712 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
713 for (auto SYNCHRONIZED_lp2 = (e2).internalDoNotUse(); \
714 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
715 for (auto& n2 = *SYNCHRONIZED_lp2.operator->(); \
716 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
717 if ((::folly::lockInOrder( \
718 SYNCHRONIZED_lp1, SYNCHRONIZED_lp2), \
722 } /* namespace folly */
724 #endif // SYNCHRONIZED_H_