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 * @author: Andrei Alexandrescu (andrei.alexandrescu@fb.com)
26 #include <folly/LockTraits.h>
27 #include <folly/Preprocessor.h>
28 #include <folly/SharedMutex.h>
29 #include <folly/Traits.h>
31 #include <type_traits>
36 enum InternalDoNotUse {};
40 * Synchronized<T> encapsulates an object of type T (a "datum") paired
41 * with a mutex. The only way to access the datum is while the mutex
42 * is locked, and Synchronized makes it virtually impossible to do
43 * otherwise. The code that would access the datum in unsafe ways
44 * would look odd and convoluted, thus readily alerting the human
45 * reviewer. In contrast, the code that uses Synchronized<T> correctly
46 * looks simple and intuitive.
48 * The second parameter must be a mutex type. Any mutex type supported by
49 * LockTraits<Mutex> can be used. By default any class with lock() and
50 * unlock() methods will work automatically. LockTraits can be specialized to
51 * teach Locked how to use other custom mutex types. See the documentation in
52 * LockTraits.h for additional details.
54 * Supported mutexes that work by default include std::mutex,
55 * std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex,
56 * folly::SharedMutex, folly::RWSpinLock, and folly::SpinLock.
57 * Include LockTraitsBoost.h to get additional LockTraits specializations to
58 * support the following boost mutex types: boost::mutex,
59 * boost::recursive_mutex, boost::shared_mutex, boost::timed_mutex, and
60 * boost::recursive_timed_mutex.
62 template <class T, class Mutex = SharedMutex>
65 * Default constructor leaves both members call their own default
68 Synchronized() = default;
71 static constexpr bool nxCopyCtor{
72 std::is_nothrow_copy_constructible<T>::value};
73 static constexpr bool nxMoveCtor{
74 std::is_nothrow_move_constructible<T>::value};
77 * Helper constructors to enable Synchronized for
78 * non-default constructible types T.
79 * Guards are created in actual public constructors and are alive
80 * for the time required to construct the object
82 template <typename Guard>
83 Synchronized(const Synchronized& rhs,
84 const Guard& /*guard*/) noexcept(nxCopyCtor)
85 : datum_(rhs.datum_) {}
87 template <typename Guard>
88 Synchronized(Synchronized&& rhs, const Guard& /*guard*/) noexcept(nxMoveCtor)
89 : datum_(std::move(rhs.datum_)) {}
93 * Copy constructor copies the data (with locking the source and
94 * all) but does NOT copy the mutex. Doing so would result in
97 Synchronized(const Synchronized& rhs) noexcept(nxCopyCtor)
98 : Synchronized(rhs, rhs.operator->()) {}
101 * Move constructor moves the data (with locking the source and all)
102 * but does not move the mutex.
104 Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor)
105 : Synchronized(std::move(rhs), rhs.operator->()) {}
108 * Constructor taking a datum as argument copies it. There is no
109 * need to lock the constructing object.
111 explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
114 * Constructor taking a datum rvalue as argument moves it. Again,
115 * there is no need to lock the constructing object.
117 explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
118 : datum_(std::move(rhs)) {}
121 * Lets you construct non-movable types in-place. Use the constexpr
122 * instance `construct_in_place` as the first argument.
124 template <typename... Args>
125 explicit Synchronized(construct_in_place_t, Args&&... args)
126 : datum_(std::forward<Args>(args)...) {}
129 * The canonical assignment operator only assigns the data, NOT the
130 * mutex. It locks the two objects in ascending order of their
133 Synchronized& operator=(const Synchronized& rhs) {
135 // Self-assignment, pass.
136 } else if (this < &rhs) {
137 auto guard1 = operator->();
138 auto guard2 = rhs.operator->();
141 auto guard1 = rhs.operator->();
142 auto guard2 = operator->();
149 * Move assignment operator, only assigns the data, NOT the
150 * mutex. It locks the two objects in ascending order of their
153 Synchronized& operator=(Synchronized&& rhs) {
155 // Self-assignment, pass.
156 } else if (this < &rhs) {
157 auto guard1 = operator->();
158 auto guard2 = rhs.operator->();
159 datum_ = std::move(rhs.datum_);
161 auto guard1 = rhs.operator->();
162 auto guard2 = operator->();
163 datum_ = std::move(rhs.datum_);
169 * Lock object, assign datum.
171 Synchronized& operator=(const T& rhs) {
172 auto guard = operator->();
178 * Lock object, move-assign datum.
180 Synchronized& operator=(T&& rhs) {
181 auto guard = operator->();
182 datum_ = std::move(rhs);
187 * A LockedPtr lp keeps a modifiable (i.e. non-const)
188 * Synchronized<T> object locked for the duration of lp's
189 * existence. Because of this, you get to access the datum's methods
190 * directly by using lp->fun().
194 * Found no reason to leave this hanging.
196 LockedPtr() = delete;
199 * Takes a Synchronized and locks it.
201 explicit LockedPtr(Synchronized* parent) : parent_(parent) {
206 * Takes a Synchronized and attempts to lock it for some
207 * milliseconds. If not, the LockedPtr will be subsequently null.
209 LockedPtr(Synchronized* parent, unsigned int milliseconds) {
210 std::chrono::milliseconds chronoMS(milliseconds);
211 if (LockTraits<Mutex>::try_lock_for(parent->mutex_, chronoMS)) {
215 // Could not acquire the resource, pointer is null
220 * This is used ONLY inside SYNCHRONIZED_DUAL. It initializes
221 * everything properly, but does not lock the parent because it
222 * "knows" someone else will lock it. Please do not use.
224 LockedPtr(Synchronized* parent, detail::InternalDoNotUse)
229 * Copy ctor adds one lock.
231 LockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
236 * Assigning from another LockedPtr results in freeing the former
237 * lock and acquiring the new one. The method works with
238 * self-assignment (does nothing).
240 LockedPtr& operator=(const LockedPtr& rhs) {
241 if (parent_ != rhs.parent_) {
242 if (parent_) parent_->mutex_.unlock();
243 parent_ = rhs.parent_;
250 * Destructor releases.
254 LockTraits<Mutex>::unlock(parent_->mutex_);
259 * Safe to access the data. Don't save the obtained pointer by
260 * invoking lp.operator->() by hand. Also, if the method returns a
261 * handle stored inside the datum, don't use this idiom - use
262 * SYNCHRONIZED below.
265 return parent_ ? &parent_->datum_ : nullptr;
269 * This class temporarily unlocks a LockedPtr in a scoped
270 * manner. It is used inside of the UNSYNCHRONIZED macro.
272 struct Unsynchronizer {
273 explicit Unsynchronizer(LockedPtr* p) : parent_(p) {
274 LockTraits<Mutex>::unlock(parent_->parent_->mutex_);
276 Unsynchronizer(const Unsynchronizer&) = delete;
277 Unsynchronizer& operator=(const Unsynchronizer&) = delete;
281 LockedPtr* operator->() const {
287 friend struct Unsynchronizer;
288 Unsynchronizer typeHackDoNotUse();
290 template <class P1, class P2>
291 friend void lockInOrder(P1& p1, P2& p2);
296 LockTraits<Mutex>::lock(parent_->mutex_);
300 // This is the entire state of LockedPtr.
301 Synchronized* parent_;
305 * ConstLockedPtr does exactly what LockedPtr does, but for const
306 * Synchronized objects. Of interest is that ConstLockedPtr only
307 * uses a read lock, which is faster but more restrictive - you only
308 * get to call const methods of the datum.
310 * Much of the code between LockedPtr and
311 * ConstLockedPtr is identical and could be factor out, but there
312 * are enough nagging little differences to not justify the trouble.
314 struct ConstLockedPtr {
315 ConstLockedPtr() = delete;
316 explicit ConstLockedPtr(const Synchronized* parent) : parent_(parent) {
319 ConstLockedPtr(const Synchronized* parent, detail::InternalDoNotUse)
322 ConstLockedPtr(const ConstLockedPtr& rhs) : parent_(rhs.parent_) {
325 explicit ConstLockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
328 ConstLockedPtr(const Synchronized* parent, unsigned int milliseconds) {
329 if (try_lock_shared_or_unique_for(
330 parent->mutex_, std::chrono::milliseconds(milliseconds))) {
334 // Could not acquire the resource, pointer is null
338 ConstLockedPtr& operator=(const ConstLockedPtr& rhs) {
339 if (parent_ != rhs.parent_) {
340 if (parent_) parent_->mutex_.unlock_shared();
341 parent_ = rhs.parent_;
347 unlock_shared_or_unique(parent_->mutex_);
351 const T* operator->() const {
352 return parent_ ? &parent_->datum_ : nullptr;
355 struct Unsynchronizer {
356 explicit Unsynchronizer(ConstLockedPtr* p) : parent_(p) {
357 unlock_shared_or_unique(parent_->parent_->mutex_);
359 Unsynchronizer(const Unsynchronizer&) = delete;
360 Unsynchronizer& operator=(const Unsynchronizer&) = delete;
362 lock_shared_or_unique(parent_->parent_->mutex_);
364 ConstLockedPtr* operator->() const {
368 ConstLockedPtr* parent_;
370 friend struct Unsynchronizer;
371 Unsynchronizer typeHackDoNotUse();
373 template <class P1, class P2>
374 friend void lockInOrder(P1& p1, P2& p2);
379 lock_shared_or_unique(parent_->mutex_);
383 const Synchronized* parent_;
387 * This accessor offers a LockedPtr. In turn. LockedPtr offers
388 * operator-> returning a pointer to T. The operator-> keeps
389 * expanding until it reaches a pointer, so syncobj->foo() will lock
390 * the object and call foo() against it.
392 LockedPtr operator->() {
393 return LockedPtr(this);
397 * Same, for constant objects. You will be able to invoke only const
400 ConstLockedPtr operator->() const {
401 return ConstLockedPtr(this);
405 * Attempts to acquire for a given number of milliseconds. If
406 * acquisition is unsuccessful, the returned LockedPtr is NULL.
408 LockedPtr timedAcquire(unsigned int milliseconds) {
409 return LockedPtr(this, milliseconds);
413 * As above, for a constant object.
415 ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
416 return ConstLockedPtr(this, milliseconds);
420 * Used by SYNCHRONIZED_DUAL.
422 LockedPtr internalDoNotUse() {
423 return LockedPtr(this, detail::InternalDoNotUse());
429 ConstLockedPtr internalDoNotUse() const {
430 return ConstLockedPtr(this, detail::InternalDoNotUse());
434 * Sometimes, although you have a mutable object, you only want to
435 * call a const method against it. The most efficient way to achieve
436 * that is by using a read lock. You get to do so by using
437 * obj.asConst()->method() instead of obj->method().
439 const Synchronized& asConst() const {
444 * Swaps with another Synchronized. Protected against
445 * self-swap. Only data is swapped. Locks are acquired in increasing
448 void swap(Synchronized& rhs) {
453 return rhs.swap(*this);
455 auto guard1 = operator->();
456 auto guard2 = rhs.operator->();
459 swap(datum_, rhs.datum_);
463 * Swap with another datum. Recommended because it keeps the mutex
467 LockedPtr guard = operator->();
474 * Copies datum to a given target.
476 void copy(T* target) const {
477 ConstLockedPtr guard = operator->();
482 * Returns a fresh copy of the datum.
485 ConstLockedPtr guard = operator->();
491 mutable Mutex mutex_;
494 // Non-member swap primitive
495 template <class T, class M>
496 void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
501 * SYNCHRONIZED is the main facility that makes Synchronized<T>
502 * helpful. It is a pseudo-statement that introduces a scope where the
503 * object is locked. Inside that scope you get to access the unadorned
508 * Synchronized<vector<int>> svector;
510 * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
512 * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
514 * Refer to folly/docs/Synchronized.md for a detailed explanation and more
517 #define SYNCHRONIZED(...) \
519 FOLLY_GCC_DISABLE_WARNING(shadow) \
520 if (bool SYNCHRONIZED_state = false) { \
522 for (auto SYNCHRONIZED_lockedPtr = \
523 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \
524 !SYNCHRONIZED_state; \
525 SYNCHRONIZED_state = true) \
526 for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
527 *SYNCHRONIZED_lockedPtr.operator->(); \
528 !SYNCHRONIZED_state; \
529 SYNCHRONIZED_state = true) \
532 #define TIMED_SYNCHRONIZED(timeout, ...) \
533 if (bool SYNCHRONIZED_state = false) { \
535 for (auto SYNCHRONIZED_lockedPtr = \
536 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \
537 !SYNCHRONIZED_state; \
538 SYNCHRONIZED_state = true) \
539 for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
540 SYNCHRONIZED_lockedPtr.operator->(); \
541 !SYNCHRONIZED_state; \
542 SYNCHRONIZED_state = true)
545 * Similar to SYNCHRONIZED, but only uses a read lock.
547 #define SYNCHRONIZED_CONST(...) \
549 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
550 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
553 * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
555 #define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
556 TIMED_SYNCHRONIZED( \
558 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
559 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())
562 * Temporarily disables synchronization inside a SYNCHRONIZED block.
564 #define UNSYNCHRONIZED(name) \
565 for (decltype(SYNCHRONIZED_lockedPtr.typeHackDoNotUse()) \
566 SYNCHRONIZED_state3(&SYNCHRONIZED_lockedPtr); \
567 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
568 for (auto& name = *SYNCHRONIZED_state3.operator->(); \
569 !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
572 * Locks two objects in increasing order of their addresses.
574 template <class P1, class P2>
575 void lockInOrder(P1& p1, P2& p2) {
576 if (static_cast<const void*>(p1.operator->()) >
577 static_cast<const void*>(p2.operator->())) {
587 * Synchronizes two Synchronized objects (they may encapsulate
588 * different data). Synchronization is done in increasing address of
589 * object order, so there is no deadlock risk.
591 #define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
592 if (bool SYNCHRONIZED_state = false) {} else \
593 for (auto SYNCHRONIZED_lp1 = (e1).internalDoNotUse(); \
594 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
595 for (auto& n1 = *SYNCHRONIZED_lp1.operator->(); \
596 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
597 for (auto SYNCHRONIZED_lp2 = (e2).internalDoNotUse(); \
598 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
599 for (auto& n2 = *SYNCHRONIZED_lp2.operator->(); \
600 !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
601 if ((::folly::lockInOrder( \
602 SYNCHRONIZED_lp1, SYNCHRONIZED_lp2), \
606 } /* namespace folly */