1 /*
2 * Copyright 2011-present Facebook, Inc.
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16 /**
17 * This module implements a Synchronized abstraction useful in
18 * mutex-based concurrency.
19 *
20 * The Synchronized<T, Mutex> class is the primary public API exposed by this
21 * module. See folly/docs/Synchronized.md for a more complete explanation of
22 * this class and its benefits.
23 */
24
25 #pragma once
26
27 #include <folly/Function.h>
28 #include <folly/Likely.h>
29 #include <folly/LockTraits.h>
30 #include <folly/Preprocessor.h>
31 #include <folly/SharedMutex.h>
32 #include <folly/Traits.h>
33 #include <folly/Utility.h>
34 #include <folly/container/Foreach.h>
35 #include <folly/functional/ApplyTuple.h>
36 #include <glog/logging.h>
37
38 #include <array>
39 #include <mutex>
40 #include <tuple>
41 #include <type_traits>
42 #include <utility>
43
44 namespace folly {
45
46 template <class LockedType, class Mutex, class LockPolicy>
47 class LockedPtrBase;
48 template <class LockedType, class LockPolicy>
49 class LockedPtr;
50
51 /**
52 * Public version of LockInterfaceDispatcher that contains the MutexLevel enum
53 * for the passed in mutex type
54 *
55 * This is decoupled from MutexLevelValueImpl in LockTraits.h because this
56 * ensures that a heterogenous mutex with a different API can be used. For
57 * example - if a mutex does not have a lock_shared() method but the
58 * LockTraits specialization for it supports a static non member
59 * lock_shared(Mutex&) it can be used as a shared mutex and will provide
60 * rlock() and wlock() functions.
61 */
62 template <class Mutex>
63 using MutexLevelValue = detail::MutexLevelValueImpl<
64 true,
65 LockTraits<Mutex>::is_shared,
66 LockTraits<Mutex>::is_upgrade>;
67
68 /**
69 * SynchronizedBase is a helper parent class for Synchronized<T>.
70 *
71 * It provides wlock() and rlock() methods for shared mutex types,
72 * or lock() methods for purely exclusive mutex types.
73 */
74 template <class Subclass, detail::MutexLevel level>
75 class SynchronizedBase;
76
77 /**
78 * SynchronizedBase specialization for shared mutex types.
79 *
80 * This class provides wlock() and rlock() methods for acquiring the lock and
81 * accessing the data.
82 */
83 template <class Subclass>
84 class SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
85 public:
86 using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
87 using ConstWLockedPtr =
88 ::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
89 using ConstLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyShared>;
90
91 using TryWLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyTryExclusive>;
92 using ConstTryWLockedPtr =
93 ::folly::LockedPtr<const Subclass, LockPolicyTryExclusive>;
94 using TryRLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyTryShared>;
95
96 /**
97 * Acquire an exclusive lock, and return a LockedPtr that can be used to
98 * safely access the datum.
99 *
100 * LockedPtr offers operator -> and * to provide access to the datum.
101 * The lock will be released when the LockedPtr is destroyed.
102 */
wlock()103 LockedPtr wlock() {
104 return LockedPtr(static_cast<Subclass*>(this));
105 }
106
107 /**
108 * Attempts to acquire the lock in exclusive mode. If acquisition is
109 * unsuccessful, the returned LockedPtr will be null.
110 *
111 * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
112 * validity.)
113 */
tryWLock()114 TryWLockedPtr tryWLock() {
115 return TryWLockedPtr{static_cast<Subclass*>(this)};
116 }
117
118 /**
119 * Acquire a read lock, and return a ConstLockedPtr that can be used to
120 * safely access the datum.
121 */
rlock()122 ConstLockedPtr rlock() const {
123 return ConstLockedPtr(static_cast<const Subclass*>(this));
124 }
125
126 /**
127 * Attempts to acquire the lock in shared mode. If acquisition is
128 * unsuccessful, the returned LockedPtr will be null.
129 *
130 * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
131 * validity.)
132 */
tryRLock()133 TryRLockedPtr tryRLock() const {
134 return TryRLockedPtr{static_cast<const Subclass*>(this)};
135 }
136
137 /**
138 * Attempts to acquire the lock, or fails if the timeout elapses first.
139 * If acquisition is unsuccessful, the returned LockedPtr will be null.
140 *
141 * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
142 * validity.)
143 */
144 template <class Rep, class Period>
wlock(const std::chrono::duration<Rep,Period> & timeout)145 LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) {
146 return LockedPtr(static_cast<Subclass*>(this), timeout);
147 }
148
149 /**
150 * Attempts to acquire the lock, or fails if the timeout elapses first.
151 * If acquisition is unsuccessful, the returned LockedPtr will be null.
152 *
153 * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
154 * validity.)
155 */
156 template <class Rep, class Period>
rlock(const std::chrono::duration<Rep,Period> & timeout)157 ConstLockedPtr rlock(
158 const std::chrono::duration<Rep, Period>& timeout) const {
159 return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
160 }
161
162 /**
163 * Invoke a function while holding the lock exclusively.
164 *
165 * A reference to the datum will be passed into the function as its only
166 * argument.
167 *
168 * This can be used with a lambda argument for easily defining small critical
169 * sections in the code. For example:
170 *
171 * auto value = obj.withWLock([](auto& data) {
172 * data.doStuff();
173 * return data.getValue();
174 * });
175 */
176 template <class Function>
withWLock(Function && function)177 auto withWLock(Function&& function) {
178 return function(*wlock());
179 }
180
181 /**
182 * Invoke a function while holding the lock exclusively.
183 *
184 * This is similar to withWLock(), but the function will be passed a
185 * LockedPtr rather than a reference to the data itself.
186 *
187 * This allows scopedUnlock() to be called on the LockedPtr argument if
188 * desired.
189 */
190 template <class Function>
withWLockPtr(Function && function)191 auto withWLockPtr(Function&& function) {
192 return function(wlock());
193 }
194
195 /**
196 * Invoke a function while holding an the lock in shared mode.
197 *
198 * A const reference to the datum will be passed into the function as its
199 * only argument.
200 */
201 template <class Function>
withRLock(Function && function)202 auto withRLock(Function&& function) const {
203 return function(*rlock());
204 }
205
206 template <class Function>
withRLockPtr(Function && function)207 auto withRLockPtr(Function&& function) const {
208 return function(rlock());
209 }
210 };
211
212 /**
213 * SynchronizedBase specialization for upgrade mutex types.
214 *
215 * This class provides all the functionality provided by the SynchronizedBase
216 * specialization for shared mutexes and a ulock() method that returns an
217 * upgradable lock RAII proxy
218 */
219 template <class Subclass>
220 class SynchronizedBase<Subclass, detail::MutexLevel::UPGRADE>
221 : public SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
222 public:
223 using UpgradeLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyUpgrade>;
224 using ConstUpgradeLockedPtr =
225 ::folly::LockedPtr<const Subclass, LockPolicyUpgrade>;
226
227 using TryUpgradeLockedPtr =
228 ::folly::LockedPtr<Subclass, LockPolicyTryUpgrade>;
229 using ConstTryUpgradeLockedPtr =
230 ::folly::LockedPtr<const Subclass, LockPolicyTryUpgrade>;
231
232 /**
233 * Acquire an upgrade lock and return a LockedPtr that can be used to safely
234 * access the datum
235 *
236 * And the const version
237 */
ulock()238 UpgradeLockedPtr ulock() {
239 return UpgradeLockedPtr(static_cast<Subclass*>(this));
240 }
241
242 /**
243 * Attempts to acquire the lock in upgrade mode. If acquisition is
244 * unsuccessful, the returned LockedPtr will be null.
245 *
246 * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
247 * validity.)
248 */
tryULock()249 TryUpgradeLockedPtr tryULock() {
250 return TryUpgradeLockedPtr{static_cast<Subclass*>(this)};
251 }
252
253 /**
254 * Acquire an upgrade lock and return a LockedPtr that can be used to safely
255 * access the datum
256 *
257 * And the const version
258 */
259 template <class Rep, class Period>
ulock(const std::chrono::duration<Rep,Period> & timeout)260 UpgradeLockedPtr ulock(const std::chrono::duration<Rep, Period>& timeout) {
261 return UpgradeLockedPtr(static_cast<Subclass*>(this), timeout);
262 }
263
264 /**
265 * Invoke a function while holding the lock.
266 *
267 * A reference to the datum will be passed into the function as its only
268 * argument.
269 *
270 * This can be used with a lambda argument for easily defining small critical
271 * sections in the code. For example:
272 *
273 * auto value = obj.withULock([](auto& data) {
274 * data.doStuff();
275 * return data.getValue();
276 * });
277 *
278 * This is probably not the function you want. If the intent is to read the
279 * data object and determine whether you should upgrade to a write lock then
280 * the withULockPtr() method should be called instead, since it gives access
281 * to the LockedPtr proxy (which can be upgraded via the
282 * moveFromUpgradeToWrite() method)
283 */
284 template <class Function>
withULock(Function && function)285 auto withULock(Function&& function) {
286 return function(*ulock());
287 }
288
289 /**
290 * Invoke a function while holding the lock exclusively.
291 *
292 * This is similar to withULock(), but the function will be passed a
293 * LockedPtr rather than a reference to the data itself.
294 *
295 * This allows scopedUnlock() and getUniqueLock() to be called on the
296 * LockedPtr argument.
297 *
298 * This also allows you to upgrade the LockedPtr proxy to a write state so
299 * that changes can be made to the underlying data
300 */
301 template <class Function>
withULockPtr(Function && function)302 auto withULockPtr(Function&& function) {
303 return function(ulock());
304 }
305 };
306
307 /**
308 * SynchronizedBase specialization for non-shared mutex types.
309 *
310 * This class provides lock() methods for acquiring the lock and accessing the
311 * data.
312 */
313 template <class Subclass>
314 class SynchronizedBase<Subclass, detail::MutexLevel::UNIQUE> {
315 public:
316 using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
317 using ConstLockedPtr =
318 ::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
319
320 using TryLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyTryExclusive>;
321 using ConstTryLockedPtr =
322 ::folly::LockedPtr<const Subclass, LockPolicyTryExclusive>;
323
324 /**
325 * Acquire a lock, and return a LockedPtr that can be used to safely access
326 * the datum.
327 */
lock()328 LockedPtr lock() {
329 return LockedPtr(static_cast<Subclass*>(this));
330 }
331
332 /**
333 * Acquire a lock, and return a ConstLockedPtr that can be used to safely
334 * access the datum.
335 */
lock()336 ConstLockedPtr lock() const {
337 return ConstLockedPtr(static_cast<const Subclass*>(this));
338 }
339
340 /**
341 * Attempts to acquire the lock in exclusive mode. If acquisition is
342 * unsuccessful, the returned LockedPtr will be null.
343 *
344 * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for
345 * validity.)
346 */
tryLock()347 TryLockedPtr tryLock() {
348 return TryLockedPtr{static_cast<Subclass*>(this)};
349 }
tryLock()350 ConstTryLockedPtr tryLock() const {
351 return ConstTryLockedPtr{static_cast<const Subclass*>(this)};
352 }
353
354 /**
355 * Attempts to acquire the lock, or fails if the timeout elapses first.
356 * If acquisition is unsuccessful, the returned LockedPtr will be null.
357 */
358 template <class Rep, class Period>
lock(const std::chrono::duration<Rep,Period> & timeout)359 LockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) {
360 return LockedPtr(static_cast<Subclass*>(this), timeout);
361 }
362
363 /**
364 * Attempts to acquire the lock, or fails if the timeout elapses first.
365 * If acquisition is unsuccessful, the returned LockedPtr will be null.
366 */
367 template <class Rep, class Period>
lock(const std::chrono::duration<Rep,Period> & timeout)368 ConstLockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) const {
369 return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
370 }
371
372 /**
373 * Invoke a function while holding the lock.
374 *
375 * A reference to the datum will be passed into the function as its only
376 * argument.
377 *
378 * This can be used with a lambda argument for easily defining small critical
379 * sections in the code. For example:
380 *
381 * auto value = obj.withLock([](auto& data) {
382 * data.doStuff();
383 * return data.getValue();
384 * });
385 */
386 template <class Function>
withLock(Function && function)387 auto withLock(Function&& function) {
388 return function(*lock());
389 }
390 template <class Function>
withLock(Function && function)391 auto withLock(Function&& function) const {
392 return function(*lock());
393 }
394
395 /**
396 * Invoke a function while holding the lock exclusively.
397 *
398 * This is similar to withWLock(), but the function will be passed a
399 * LockedPtr rather than a reference to the data itself.
400 *
401 * This allows scopedUnlock() and getUniqueLock() to be called on the
402 * LockedPtr argument.
403 */
404 template <class Function>
withLockPtr(Function && function)405 auto withLockPtr(Function&& function) {
406 return function(lock());
407 }
408 template <class Function>
withLockPtr(Function && function)409 auto withLockPtr(Function&& function) const {
410 return function(lock());
411 }
412 };
413
414 /**
415 * Synchronized<T> encapsulates an object of type T (a "datum") paired
416 * with a mutex. The only way to access the datum is while the mutex
417 * is locked, and Synchronized makes it virtually impossible to do
418 * otherwise. The code that would access the datum in unsafe ways
419 * would look odd and convoluted, thus readily alerting the human
420 * reviewer. In contrast, the code that uses Synchronized<T> correctly
421 * looks simple and intuitive.
422 *
423 * The second parameter must be a mutex type. Any mutex type supported by
424 * LockTraits<Mutex> can be used. By default any class with lock() and
425 * unlock() methods will work automatically. LockTraits can be specialized to
426 * teach Synchronized how to use other custom mutex types. See the
427 * documentation in LockTraits.h for additional details.
428 *
429 * Supported mutexes that work by default include std::mutex,
430 * std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex,
431 * folly::SharedMutex, folly::RWSpinLock, and folly::SpinLock.
432 * Include LockTraitsBoost.h to get additional LockTraits specializations to
433 * support the following boost mutex types: boost::mutex,
434 * boost::recursive_mutex, boost::shared_mutex, boost::timed_mutex, and
435 * boost::recursive_timed_mutex.
436 */
437 template <class T, class Mutex = SharedMutex>
438 struct Synchronized : public SynchronizedBase<
439 Synchronized<T, Mutex>,
440 MutexLevelValue<Mutex>::value> {
441 private:
442 using Base =
443 SynchronizedBase<Synchronized<T, Mutex>, MutexLevelValue<Mutex>::value>;
444 static constexpr bool nxCopyCtor{
445 std::is_nothrow_copy_constructible<T>::value};
446 static constexpr bool nxMoveCtor{
447 std::is_nothrow_move_constructible<T>::value};
448
449 // used to disable copy construction and assignment
450 class NonImplementedType;
451
452 public:
453 using LockedPtr = typename Base::LockedPtr;
454 using ConstLockedPtr = typename Base::ConstLockedPtr;
455 using DataType = T;
456 using MutexType = Mutex;
457
458 /**
459 * Default constructor leaves both members call their own default
460 * constructor.
461 */
462 Synchronized() = default;
463
464 public:
465 /**
466 * Copy constructor; deprecated
467 *
468 * Enabled only when the data type is copy-constructible.
469 *
470 * Takes a shared-or-exclusive lock on the source mutex while performing the
471 * copy-construction of the destination data from the source data. No lock is
472 * taken on the destination mutex.
473 *
474 * May throw even when the data type is is nothrow-copy-constructible because
475 * acquiring a lock may throw.
476 */
SynchronizedSynchronized477 /* implicit */ Synchronized(typename std::conditional<
478 std::is_copy_constructible<T>::value,
479 const Synchronized&,
480 NonImplementedType>::type rhs) /* may throw */
481 : Synchronized(rhs.copy()) {}
482
483 /**
484 * Move constructor; deprecated
485 *
486 * Move-constructs from the source data without locking either the source or
487 * the destination mutex.
488 *
489 * Semantically, assumes that the source object is a true rvalue and therefore
490 * that no synchronization is required for accessing it.
491 */
noexceptSynchronized492 Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor)
493 : Synchronized(std::move(rhs.datum_)) {}
494
495 /**
496 * Constructor taking a datum as argument copies it. There is no
497 * need to lock the constructing object.
498 */
SynchronizedSynchronized499 explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
500
501 /**
502 * Constructor taking a datum rvalue as argument moves it. Again,
503 * there is no need to lock the constructing object.
504 */
SynchronizedSynchronized505 explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
506 : datum_(std::move(rhs)) {}
507
508 /**
509 * Lets you construct non-movable types in-place. Use the constexpr
510 * instance `in_place` as the first argument.
511 */
512 template <typename... Args>
SynchronizedSynchronized513 explicit Synchronized(in_place_t, Args&&... args)
514 : datum_(std::forward<Args>(args)...) {}
515
516 /**
517 * Lets you construct the synchronized object and also pass construction
518 * parameters to the underlying mutex if desired
519 */
520 template <typename... DatumArgs, typename... MutexArgs>
SynchronizedSynchronized521 Synchronized(
522 std::piecewise_construct_t,
523 std::tuple<DatumArgs...> datumArgs,
524 std::tuple<MutexArgs...> mutexArgs)
525 : Synchronized{std::piecewise_construct,
526 std::move(datumArgs),
527 std::move(mutexArgs),
528 make_index_sequence<sizeof...(DatumArgs)>{},
529 make_index_sequence<sizeof...(MutexArgs)>{}} {}
530
531 /**
532 * Copy assignment operator; deprecated
533 *
534 * Enabled only when the data type is copy-constructible and move-assignable.
535 *
536 * Move-assigns from a copy of the source data.
537 *
538 * Takes a shared-or-exclusive lock on the source mutex while copying the
539 * source data to a temporary. Takes an exclusive lock on the destination
540 * mutex while move-assigning from the temporary.
541 *
542 * This technique consts an extra temporary but avoids the need to take locks
543 * on both mutexes together.
544 */
545 Synchronized& operator=(typename std::conditional<
546 std::is_copy_constructible<T>::value &&
547 std::is_move_assignable<T>::value,
548 const Synchronized&,
549 NonImplementedType>::type rhs) {
550 return *this = rhs.copy();
551 }
552
553 /**
554 * Move assignment operator; deprecated
555 *
556 * Takes an exclusive lock on the destination mutex while move-assigning the
557 * destination data from the source data. The source mutex is not locked or
558 * otherwise accessed.
559 *
560 * Semantically, assumes that the source object is a true rvalue and therefore
561 * that no synchronization is required for accessing it.
562 */
563 Synchronized& operator=(Synchronized&& rhs) {
564 return *this = std::move(rhs.datum_);
565 }
566
567 /**
568 * Lock object, assign datum.
569 */
570 Synchronized& operator=(const T& rhs) {
571 if (&datum_ != &rhs) {
572 auto guard = operator->();
573 datum_ = rhs;
574 }
575 return *this;
576 }
577
578 /**
579 * Lock object, move-assign datum.
580 */
581 Synchronized& operator=(T&& rhs) {
582 if (&datum_ != &rhs) {
583 auto guard = operator->();
584 datum_ = std::move(rhs);
585 }
586 return *this;
587 }
588
589 /**
590 * Acquire an appropriate lock based on the context.
591 *
592 * If the mutex is a shared mutex, and the Synchronized instance is const,
593 * this acquires a shared lock. Otherwise this acquires an exclusive lock.
594 *
595 * In general, prefer using the explicit rlock() and wlock() methods
596 * for read-write locks, and lock() for purely exclusive locks.
597 *
598 * contextualLock() is primarily intended for use in other template functions
599 * that do not necessarily know the lock type.
600 */
contextualLockSynchronized601 LockedPtr contextualLock() {
602 return LockedPtr(this);
603 }
contextualLockSynchronized604 ConstLockedPtr contextualLock() const {
605 return ConstLockedPtr(this);
606 }
607 template <class Rep, class Period>
contextualLockSynchronized608 LockedPtr contextualLock(const std::chrono::duration<Rep, Period>& timeout) {
609 return LockedPtr(this, timeout);
610 }
611 template <class Rep, class Period>
contextualLockSynchronized612 ConstLockedPtr contextualLock(
613 const std::chrono::duration<Rep, Period>& timeout) const {
614 return ConstLockedPtr(this, timeout);
615 }
616 /**
617 * contextualRLock() acquires a read lock if the mutex type is shared,
618 * or a regular exclusive lock for non-shared mutex types.
619 *
620 * contextualRLock() when you know that you prefer a read lock (if
621 * available), even if the Synchronized<T> object itself is non-const.
622 */
contextualRLockSynchronized623 ConstLockedPtr contextualRLock() const {
624 return ConstLockedPtr(this);
625 }
626 template <class Rep, class Period>
contextualRLockSynchronized627 ConstLockedPtr contextualRLock(
628 const std::chrono::duration<Rep, Period>& timeout) const {
629 return ConstLockedPtr(this, timeout);
630 }
631
632 /**
633 * This accessor offers a LockedPtr. In turn, LockedPtr offers
634 * operator-> returning a pointer to T. The operator-> keeps
635 * expanding until it reaches a pointer, so syncobj->foo() will lock
636 * the object and call foo() against it.
637 *
638 * NOTE: This API is planned to be deprecated in an upcoming diff.
639 * Prefer using lock(), wlock(), or rlock() instead.
640 */
641 LockedPtr operator->() {
642 return LockedPtr(this);
643 }
644
645 /**
646 * Obtain a ConstLockedPtr.
647 *
648 * NOTE: This API is planned to be deprecated in an upcoming diff.
649 * Prefer using lock(), wlock(), or rlock() instead.
650 */
651 ConstLockedPtr operator->() const {
652 return ConstLockedPtr(this);
653 }
654
655 /**
656 * Attempts to acquire for a given number of milliseconds. If
657 * acquisition is unsuccessful, the returned LockedPtr is nullptr.
658 *
659 * NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead.
660 * In the future it will be marked with a deprecation attribute to emit
661 * build-time warnings, and then it will be removed entirely.
662 */
timedAcquireSynchronized663 LockedPtr timedAcquire(unsigned int milliseconds) {
664 return LockedPtr(this, std::chrono::milliseconds(milliseconds));
665 }
666
667 /**
668 * Attempts to acquire for a given number of milliseconds. If
669 * acquisition is unsuccessful, the returned ConstLockedPtr is nullptr.
670 *
671 * NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead.
672 * In the future it will be marked with a deprecation attribute to emit
673 * build-time warnings, and then it will be removed entirely.
674 */
timedAcquireSynchronized675 ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
676 return ConstLockedPtr(this, std::chrono::milliseconds(milliseconds));
677 }
678
679 /**
680 * Swaps with another Synchronized. Protected against
681 * self-swap. Only data is swapped. Locks are acquired in increasing
682 * address order.
683 */
swapSynchronized684 void swap(Synchronized& rhs) {
685 if (this == &rhs) {
686 return;
687 }
688 if (this > &rhs) {
689 return rhs.swap(*this);
690 }
691 auto guard1 = operator->();
692 auto guard2 = rhs.operator->();
693
694 using std::swap;
695 swap(datum_, rhs.datum_);
696 }
697
698 /**
699 * Swap with another datum. Recommended because it keeps the mutex
700 * held only briefly.
701 */
swapSynchronized702 void swap(T& rhs) {
703 LockedPtr guard(this);
704
705 using std::swap;
706 swap(datum_, rhs);
707 }
708
709 /**
710 * Assign another datum and return the original value. Recommended
711 * because it keeps the mutex held only briefly.
712 */
exchangeSynchronized713 T exchange(T&& rhs) {
714 swap(rhs);
715 return std::move(rhs);
716 }
717
718 /**
719 * Copies datum to a given target.
720 */
copySynchronized721 void copy(T* target) const {
722 ConstLockedPtr guard(this);
723 *target = datum_;
724 }
725
726 /**
727 * Returns a fresh copy of the datum.
728 */
copySynchronized729 T copy() const {
730 ConstLockedPtr guard(this);
731 return datum_;
732 }
733
734 private:
735 template <class LockedType, class MutexType, class LockPolicy>
736 friend class folly::LockedPtrBase;
737 template <class LockedType, class LockPolicy>
738 friend class folly::LockedPtr;
739
740 /**
741 * Helper constructors to enable Synchronized for
742 * non-default constructible types T.
743 * Guards are created in actual public constructors and are alive
744 * for the time required to construct the object
745 */
SynchronizedSynchronized746 Synchronized(
747 const Synchronized& rhs,
748 const ConstLockedPtr& /*guard*/) noexcept(nxCopyCtor)
749 : datum_(rhs.datum_) {}
750
SynchronizedSynchronized751 Synchronized(Synchronized&& rhs, const LockedPtr& /*guard*/) noexcept(
752 nxMoveCtor)
753 : datum_(std::move(rhs.datum_)) {}
754
755 template <
756 typename... DatumArgs,
757 typename... MutexArgs,
758 std::size_t... IndicesOne,
759 std::size_t... IndicesTwo>
SynchronizedSynchronized760 Synchronized(
761 std::piecewise_construct_t,
762 std::tuple<DatumArgs...> datumArgs,
763 std::tuple<MutexArgs...> mutexArgs,
764 index_sequence<IndicesOne...>,
765 index_sequence<IndicesTwo...>)
766 : datum_{std::get<IndicesOne>(std::move(datumArgs))...},
767 mutex_{std::get<IndicesTwo>(std::move(mutexArgs))...} {}
768
769 // Synchronized data members
770 T datum_;
771 mutable Mutex mutex_;
772 };
773
774 template <class SynchronizedType, class LockPolicy>
775 class ScopedUnlocker;
776
777 namespace detail {
778 /*
779 * A helper alias that resolves to "const T" if the template parameter
780 * is a const Synchronized<T>, or "T" if the parameter is not const.
781 */
782 template <class SynchronizedType>
783 using SynchronizedDataType = typename std::conditional<
784 std::is_const<SynchronizedType>::value,
785 typename SynchronizedType::DataType const,
786 typename SynchronizedType::DataType>::type;
787 /*
788 * A helper alias that resolves to a ConstLockedPtr if the template parameter
789 * is a const Synchronized<T>, or a LockedPtr if the parameter is not const.
790 */
791 template <class SynchronizedType>
792 using LockedPtrType = typename std::conditional<
793 std::is_const<SynchronizedType>::value,
794 typename SynchronizedType::ConstLockedPtr,
795 typename SynchronizedType::LockedPtr>::type;
796
797 template <
798 typename Synchronized,
799 typename LockFunc,
800 typename TryLockFunc,
801 typename... Args>
802 class SynchronizedLocker {
803 public:
804 using LockedPtr = invoke_result_t<LockFunc&, Synchronized&, const Args&...>;
805
806 template <typename LockFuncType, typename TryLockFuncType, typename... As>
SynchronizedLocker(Synchronized & sync,LockFuncType && lockFunc,TryLockFuncType tryLockFunc,As &&...as)807 SynchronizedLocker(
808 Synchronized& sync,
809 LockFuncType&& lockFunc,
810 TryLockFuncType tryLockFunc,
811 As&&... as)
812 : synchronized{sync},
813 lockFunc_{std::forward<LockFuncType>(lockFunc)},
814 tryLockFunc_{std::forward<TryLockFuncType>(tryLockFunc)},
815 args_{std::forward<As>(as)...} {}
816
lock()817 auto lock() const {
818 auto args = std::tuple<const Args&...>{args_};
819 return apply(lockFunc_, std::tuple_cat(std::tie(synchronized), args));
820 }
tryLock()821 auto tryLock() const {
822 return tryLockFunc_(synchronized);
823 }
824
825 private:
826 Synchronized& synchronized;
827 LockFunc lockFunc_;
828 TryLockFunc tryLockFunc_;
829 std::tuple<Args...> args_;
830 };
831
832 template <
833 typename Synchronized,
834 typename LockFunc,
835 typename TryLockFunc,
836 typename... Args>
makeSynchronizedLocker(Synchronized & synchronized,LockFunc && lockFunc,TryLockFunc && tryLockFunc,Args &&...args)837 auto makeSynchronizedLocker(
838 Synchronized& synchronized,
839 LockFunc&& lockFunc,
840 TryLockFunc&& tryLockFunc,
841 Args&&... args) {
842 using LockFuncType = std::decay_t<LockFunc>;
843 using TryLockFuncType = std::decay_t<TryLockFunc>;
844 return SynchronizedLocker<
845 Synchronized,
846 LockFuncType,
847 TryLockFuncType,
848 std::decay_t<Args>...>{synchronized,
849 std::forward<LockFunc>(lockFunc),
850 std::forward<TryLockFunc>(tryLockFunc),
851 std::forward<Args>(args)...};
852 }
853
854 /**
855 * Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
856 * manner.
857 *
858 * The function uses the "smart and polite" algorithm from this link
859 * http://howardhinnant.github.io/dining_philosophers.html#Polite
860 *
861 * The gist of the algorithm is that it locks a mutex, then tries to lock the
862 * other mutexes in a non-blocking manner. If all the locks succeed, we are
863 * done, if not, we release the locks we have held, yield to allow other
864 * threads to continue and then block on the mutex that we failed to acquire.
865 *
866 * This allows dynamically yielding ownership of all the mutexes but one, so
867 * that other threads can continue doing work and locking the other mutexes.
868 * See the benchmarks in folly/test/SynchronizedBenchmark.cpp for more.
869 */
870 template <typename... SynchronizedLocker>
871 auto lock(SynchronizedLocker... lockersIn)
872 -> std::tuple<typename SynchronizedLocker::LockedPtr...> {
873 // capture the list of lockers as a tuple
874 auto lockers = std::forward_as_tuple(lockersIn...);
875
876 // make a list of null LockedPtr instances that we will return to the caller
877 auto lockedPtrs = std::tuple<typename SynchronizedLocker::LockedPtr...>{};
878
879 // start by locking the first thing in the list
880 std::get<0>(lockedPtrs) = std::get<0>(lockers).lock();
881 auto indexLocked = 0;
882
883 while (true) {
884 auto couldLockAll = true;
885
886 for_each(lockers, [&](auto& locker, auto index) {
887 // if we should try_lock on the current locker then do so
888 if (index != indexLocked) {
889 auto lockedPtr = locker.tryLock();
890
891 // if we were unable to lock this mutex,
892 //
893 // 1. release all the locks,
894 // 2. yield control to another thread to be nice
895 // 3. block on the mutex we failed to lock, acquire the lock
896 // 4. break out and set the index of the current mutex to indicate
897 // which mutex we have locked
898 if (!lockedPtr) {
899 // writing lockedPtrs = decltype(lockedPtrs){} does not compile on
900 // gcc, I believe this is a bug D7676798
901 lockedPtrs = std::tuple<typename SynchronizedLocker::LockedPtr...>{};
902
903 std::this_thread::yield();
904 fetch(lockedPtrs, index) = locker.lock();
905 indexLocked = index;
906 couldLockAll = false;
907
908 return loop_break;
909 }
910
911 // else store the locked mutex in the list we return
912 fetch(lockedPtrs, index) = std::move(lockedPtr);
913 }
914
915 return loop_continue;
916 });
917
918 if (couldLockAll) {
919 return lockedPtrs;
920 }
921 }
922 }
923
924 template <typename Synchronized, typename... Args>
wlock(Synchronized & synchronized,Args &&...args)925 auto wlock(Synchronized& synchronized, Args&&... args) {
926 return detail::makeSynchronizedLocker(
927 synchronized,
928 [](auto& s, auto&&... a) {
929 return s.wlock(std::forward<decltype(a)>(a)...);
930 },
931 [](auto& s) { return s.tryWLock(); },
932 std::forward<Args>(args)...);
933 }
934 template <typename Synchronized, typename... Args>
rlock(Synchronized & synchronized,Args &&...args)935 auto rlock(Synchronized& synchronized, Args&&... args) {
936 return detail::makeSynchronizedLocker(
937 synchronized,
938 [](auto& s, auto&&... a) {
939 return s.rlock(std::forward<decltype(a)>(a)...);
940 },
941 [](auto& s) { return s.tryRLock(); },
942 std::forward<Args>(args)...);
943 }
944 template <typename Synchronized, typename... Args>
ulock(Synchronized & synchronized,Args &&...args)945 auto ulock(Synchronized& synchronized, Args&&... args) {
946 return detail::makeSynchronizedLocker(
947 synchronized,
948 [](auto& s, auto&&... a) {
949 return s.ulock(std::forward<decltype(a)>(a)...);
950 },
951 [](auto& s) { return s.tryULock(); },
952 std::forward<Args>(args)...);
953 }
954 template <typename Synchronized, typename... Args>
lock(Synchronized & synchronized,Args &&...args)955 auto lock(Synchronized& synchronized, Args&&... args) {
956 return detail::makeSynchronizedLocker(
957 synchronized,
958 [](auto& s, auto&&... a) {
959 return s.lock(std::forward<decltype(a)>(a)...);
960 },
961 [](auto& s) { return s.tryLock(); },
962 std::forward<Args>(args)...);
963 }
964
965 } // namespace detail
966
967 /**
968 * A helper base class for implementing LockedPtr.
969 *
970 * The main reason for having this as a separate class is so we can specialize
971 * it for std::mutex, so we can expose a std::unique_lock to the caller
972 * when std::mutex is being used. This allows callers to use a
973 * std::condition_variable with the mutex from a Synchronized<T, std::mutex>.
974 *
975 * We don't use std::unique_lock with other Mutex types since it makes the
976 * LockedPtr class slightly larger, and it makes the logic to support
977 * ScopedUnlocker slightly more complicated. std::mutex is the only one that
978 * really seems to benefit from the unique_lock. std::condition_variable
979 * itself only supports std::unique_lock<std::mutex>, so there doesn't seem to
980 * be any real benefit to exposing the unique_lock with other mutex types.
981 *
982 * Note that the SynchronizedType template parameter may or may not be const
983 * qualified.
984 */
985 template <class SynchronizedType, class Mutex, class LockPolicy>
986 class LockedPtrBase {
987 public:
988 using MutexType = Mutex;
989 friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
990
991 /**
992 * Friend all instantiations of LockedPtr and LockedPtrBase
993 */
994 template <typename S, typename L>
995 friend class folly::LockedPtr;
996 template <typename S, typename M, typename L>
997 friend class LockedPtrBase;
998
999 /**
1000 * Destructor releases.
1001 */
~LockedPtrBase()1002 ~LockedPtrBase() {
1003 if (parent_) {
1004 LockPolicy::unlock(parent_->mutex_);
1005 }
1006 }
1007
1008 /**
1009 * Unlock the synchronized data.
1010 *
1011 * The LockedPtr can no longer be dereferenced after unlock() has been
1012 * called. isValid() will return false on an unlocked LockedPtr.
1013 *
1014 * unlock() can only be called on a LockedPtr that is valid.
1015 */
unlock()1016 void unlock() {
1017 DCHECK(parent_ != nullptr);
1018 LockPolicy::unlock(parent_->mutex_);
1019 parent_ = nullptr;
1020 }
1021
1022 protected:
LockedPtrBase()1023 LockedPtrBase() {}
LockedPtrBase(SynchronizedType * parent)1024 explicit LockedPtrBase(SynchronizedType* parent) : parent_(parent) {
1025 DCHECK(parent);
1026 if (!LockPolicy::lock(parent_->mutex_)) {
1027 parent_ = nullptr;
1028 }
1029 }
1030 template <class Rep, class Period>
LockedPtrBase(SynchronizedType * parent,const std::chrono::duration<Rep,Period> & timeout)1031 LockedPtrBase(
1032 SynchronizedType* parent,
1033 const std::chrono::duration<Rep, Period>& timeout) {
1034 if (LockPolicy::try_lock_for(parent->mutex_, timeout)) {
1035 this->parent_ = parent;
1036 }
1037 }
LockedPtrBase(LockedPtrBase && rhs)1038 LockedPtrBase(LockedPtrBase&& rhs) noexcept
1039 : parent_{exchange(rhs.parent_, nullptr)} {}
1040 LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
1041 assignImpl(*this, rhs);
1042 return *this;
1043 }
1044
1045 /**
1046 * Templated move construct and assignment operators
1047 *
1048 * These allow converting LockedPtr types that have the same unlocking
1049 * policy to each other. This allows us to write code like
1050 *
1051 * auto wlock = sync.wlock();
1052 * wlock.unlock();
1053 *
1054 * auto ulock = sync.ulock();
1055 * wlock = ulock.moveFromUpgradeToWrite();
1056 */
1057 template <typename LockPolicyType>
LockedPtrBase(LockedPtrBase<SynchronizedType,Mutex,LockPolicyType> && rhs)1058 LockedPtrBase(
1059 LockedPtrBase<SynchronizedType, Mutex, LockPolicyType>&& rhs) noexcept
1060 : parent_{exchange(rhs.parent_, nullptr)} {}
1061 template <typename LockPolicyType>
1062 LockedPtrBase& operator=(
1063 LockedPtrBase<SynchronizedType, Mutex, LockPolicyType>&& rhs) noexcept {
1064 assignImpl(*this, rhs);
1065 return *this;
1066 }
1067
1068 /**
1069 * Implementation for the assignment operator
1070 */
1071 template <typename LockPolicyLhs, typename LockPolicyRhs>
assignImpl(LockedPtrBase<SynchronizedType,Mutex,LockPolicyLhs> & lhs,LockedPtrBase<SynchronizedType,Mutex,LockPolicyRhs> & rhs)1072 void assignImpl(
1073 LockedPtrBase<SynchronizedType, Mutex, LockPolicyLhs>& lhs,
1074 LockedPtrBase<SynchronizedType, Mutex, LockPolicyRhs>& rhs) noexcept {
1075 if (lhs.parent_) {
1076 LockPolicy::unlock(lhs.parent_->mutex_);
1077 }
1078
1079 lhs.parent_ = exchange(rhs.parent_, nullptr);
1080 }
1081
1082 using UnlockerData = SynchronizedType*;
1083
1084 /**
1085 * Get a pointer to the Synchronized object from the UnlockerData.
1086 *
1087 * In the generic case UnlockerData is just the Synchronized pointer,
1088 * so we return it as is. (This function is more interesting in the
1089 * std::mutex specialization below.)
1090 */
getSynchronized(UnlockerData data)1091 static SynchronizedType* getSynchronized(UnlockerData data) {
1092 return data;
1093 }
1094
releaseLock()1095 UnlockerData releaseLock() {
1096 DCHECK(parent_ != nullptr);
1097 auto current = parent_;
1098 parent_ = nullptr;
1099 LockPolicy::unlock(current->mutex_);
1100 return current;
1101 }
reacquireLock(UnlockerData && data)1102 void reacquireLock(UnlockerData&& data) {
1103 DCHECK(parent_ == nullptr);
1104 parent_ = data;
1105 LockPolicy::lock(parent_->mutex_);
1106 }
1107
1108 SynchronizedType* parent_ = nullptr;
1109 };
1110
1111 /**
1112 * LockedPtrBase specialization for use with std::mutex.
1113 *
1114 * When std::mutex is used we use a std::unique_lock to hold the mutex.
1115 * This makes it possible to use std::condition_variable with a
1116 * Synchronized<T, std::mutex>.
1117 */
1118 template <class SynchronizedType, class LockPolicy>
1119 class LockedPtrBase<SynchronizedType, std::mutex, LockPolicy> {
1120 public:
1121 using MutexType = std::mutex;
1122 friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
1123
1124 /**
1125 * Friend all instantiations of LockedPtr and LockedPtrBase
1126 */
1127 template <typename S, typename L>
1128 friend class folly::LockedPtr;
1129 template <typename S, typename M, typename L>
1130 friend class LockedPtrBase;
1131
1132 /**
1133 * Destructor releases.
1134 */
~LockedPtrBase()1135 ~LockedPtrBase() {
1136 // The std::unique_lock will automatically release the lock when it is
1137 // destroyed, so we don't need to do anything extra here.
1138 }
1139
LockedPtrBase(LockedPtrBase && rhs)1140 LockedPtrBase(LockedPtrBase&& rhs) noexcept
1141 : lock_{std::move(rhs.lock_)}, parent_{exchange(rhs.parent_, nullptr)} {}
1142 LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
1143 assignImpl(*this, rhs);
1144 return *this;
1145 }
1146
1147 /**
1148 * Templated move construct and assignment operators
1149 *
1150 * These allow converting LockedPtr types that have the same unlocking
1151 * policy to each other.
1152 */
1153 template <typename LockPolicyType>
LockedPtrBase(LockedPtrBase<SynchronizedType,std::mutex,LockPolicyType> && other)1154 LockedPtrBase(LockedPtrBase<SynchronizedType, std::mutex, LockPolicyType>&&
1155 other) noexcept
1156 : lock_{std::move(other.lock_)},
1157 parent_{exchange(other.parent_, nullptr)} {}
1158 template <typename LockPolicyType>
1159 LockedPtrBase& operator=(
1160 LockedPtrBase<SynchronizedType, std::mutex, LockPolicyType>&&
1161 rhs) noexcept {
1162 assignImpl(*this, rhs);
1163 return *this;
1164 }
1165
1166 /**
1167 * Implementation for the assignment operator
1168 */
1169 template <typename LockPolicyLhs, typename LockPolicyRhs>
assignImpl(LockedPtrBase<SynchronizedType,std::mutex,LockPolicyLhs> & lhs,LockedPtrBase<SynchronizedType,std::mutex,LockPolicyRhs> & rhs)1170 void assignImpl(
1171 LockedPtrBase<SynchronizedType, std::mutex, LockPolicyLhs>& lhs,
1172 LockedPtrBase<SynchronizedType, std::mutex, LockPolicyRhs>&
1173 rhs) noexcept {
1174 lhs.lock_ = std::move(rhs.lock_);
1175 lhs.parent_ = exchange(rhs.parent_, nullptr);
1176 }
1177
1178 /**
1179 * Get a reference to the std::unique_lock.
1180 *
1181 * This is provided so that callers can use Synchronized<T, std::mutex>
1182 * with a std::condition_variable.
1183 *
1184 * While this API could be used to bypass the normal Synchronized APIs and
1185 * manually interact with the underlying unique_lock, this is strongly
1186 * discouraged.
1187 */
getUniqueLock()1188 std::unique_lock<std::mutex>& getUniqueLock() {
1189 return lock_;
1190 }
1191
1192 /**
1193 * Unlock the synchronized data.
1194 *
1195 * The LockedPtr can no longer be dereferenced after unlock() has been
1196 * called. isValid() will return false on an unlocked LockedPtr.
1197 *
1198 * unlock() can only be called on a LockedPtr that is valid.
1199 */
unlock()1200 void unlock() {
1201 DCHECK(parent_ != nullptr);
1202 lock_.unlock();
1203 parent_ = nullptr;
1204 }
1205
1206 protected:
LockedPtrBase()1207 LockedPtrBase() {}
LockedPtrBase(SynchronizedType * parent)1208 explicit LockedPtrBase(SynchronizedType* parent)
1209 : lock_{parent->mutex_, std::adopt_lock}, parent_{parent} {
1210 DCHECK(parent);
1211 if (!LockPolicy::lock(parent_->mutex_)) {
1212 parent_ = nullptr;
1213 lock_.release();
1214 }
1215 }
1216
1217 using UnlockerData =
1218 std::pair<std::unique_lock<std::mutex>, SynchronizedType*>;
1219
getSynchronized(const UnlockerData & data)1220 static SynchronizedType* getSynchronized(const UnlockerData& data) {
1221 return data.second;
1222 }
1223
releaseLock()1224 UnlockerData releaseLock() {
1225 DCHECK(parent_ != nullptr);
1226 UnlockerData data(std::move(lock_), parent_);
1227 parent_ = nullptr;
1228 data.first.unlock();
1229 return data;
1230 }
reacquireLock(UnlockerData && data)1231 void reacquireLock(UnlockerData&& data) {
1232 lock_ = std::move(data.first);
1233 lock_.lock();
1234 parent_ = data.second;
1235 }
1236
1237 // The specialization for std::mutex does have to store slightly more
1238 // state than the default implementation.
1239 std::unique_lock<std::mutex> lock_;
1240 SynchronizedType* parent_ = nullptr;
1241 };
1242
1243 /**
1244 * This class temporarily unlocks a LockedPtr in a scoped manner.
1245 */
1246 template <class SynchronizedType, class LockPolicy>
1247 class ScopedUnlocker {
1248 public:
ScopedUnlocker(LockedPtr<SynchronizedType,LockPolicy> * p)1249 explicit ScopedUnlocker(LockedPtr<SynchronizedType, LockPolicy>* p)
1250 : ptr_(p), data_(ptr_->releaseLock()) {}
1251 ScopedUnlocker(const ScopedUnlocker&) = delete;
1252 ScopedUnlocker& operator=(const ScopedUnlocker&) = delete;
ScopedUnlocker(ScopedUnlocker && other)1253 ScopedUnlocker(ScopedUnlocker&& other) noexcept
1254 : ptr_(exchange(other.ptr_, nullptr)), data_(std::move(other.data_)) {}
1255 ScopedUnlocker& operator=(ScopedUnlocker&& other) = delete;
1256
~ScopedUnlocker()1257 ~ScopedUnlocker() {
1258 if (ptr_) {
1259 ptr_->reacquireLock(std::move(data_));
1260 }
1261 }
1262
1263 /**
1264 * Return a pointer to the Synchronized object used by this ScopedUnlocker.
1265 */
getSynchronized()1266 SynchronizedType* getSynchronized() const {
1267 return LockedPtr<SynchronizedType, LockPolicy>::getSynchronized(data_);
1268 }
1269
1270 private:
1271 using Data = typename LockedPtr<SynchronizedType, LockPolicy>::UnlockerData;
1272 LockedPtr<SynchronizedType, LockPolicy>* ptr_{nullptr};
1273 Data data_;
1274 };
1275
1276 /**
1277 * A LockedPtr keeps a Synchronized<T> object locked for the duration of
1278 * LockedPtr's existence.
1279 *
1280 * It provides access the datum's members directly by using operator->() and
1281 * operator*().
1282 *
1283 * The LockPolicy parameter controls whether or not the lock is acquired in
1284 * exclusive or shared mode.
1285 */
1286 template <class SynchronizedType, class LockPolicy>
1287 class LockedPtr : public LockedPtrBase<
1288 SynchronizedType,
1289 typename SynchronizedType::MutexType,
1290 LockPolicy> {
1291 private:
1292 using Base = LockedPtrBase<
1293 SynchronizedType,
1294 typename SynchronizedType::MutexType,
1295 LockPolicy>;
1296 using UnlockerData = typename Base::UnlockerData;
1297 // CDataType is the DataType with the appropriate const-qualification
1298 using CDataType = detail::SynchronizedDataType<SynchronizedType>;
1299 // Enable only if the unlock policy of the other LockPolicy is the same as
1300 // ours
1301 template <typename LockPolicyOther>
1302 using EnableIfSameUnlockPolicy = std::enable_if_t<std::is_same<
1303 typename LockPolicy::UnlockPolicy,
1304 typename LockPolicyOther::UnlockPolicy>::value>;
1305
1306 // friend other LockedPtr types
1307 template <typename SynchronizedTypeOther, typename LockPolicyOther>
1308 friend class LockedPtr;
1309
1310 public:
1311 using DataType = typename SynchronizedType::DataType;
1312 using MutexType = typename SynchronizedType::MutexType;
1313 using Synchronized = typename std::remove_const<SynchronizedType>::type;
1314 friend class ScopedUnlocker<SynchronizedType, LockPolicy>;
1315
1316 /**
1317 * Creates an uninitialized LockedPtr.
1318 *
1319 * Dereferencing an uninitialized LockedPtr is not allowed.
1320 */
LockedPtr()1321 LockedPtr() {}
1322
1323 /**
1324 * Takes a Synchronized<T> and locks it.
1325 */
LockedPtr(SynchronizedType * parent)1326 explicit LockedPtr(SynchronizedType* parent) : Base(parent) {}
1327
1328 /**
1329 * Takes a Synchronized<T> and attempts to lock it, within the specified
1330 * timeout.
1331 *
1332 * Blocks until the lock is acquired or until the specified timeout expires.
1333 * If the timeout expired without acquiring the lock, the LockedPtr will be
1334 * null, and LockedPtr::isNull() will return true.
1335 */
1336 template <class Rep, class Period>
LockedPtr(SynchronizedType * parent,const std::chrono::duration<Rep,Period> & timeout)1337 LockedPtr(
1338 SynchronizedType* parent,
1339 const std::chrono::duration<Rep, Period>& timeout)
1340 : Base(parent, timeout) {}
1341
1342 /**
1343 * Move constructor.
1344 */
1345 LockedPtr(LockedPtr&& rhs) noexcept = default;
1346 template <
1347 typename LockPolicyType,
1348 EnableIfSameUnlockPolicy<LockPolicyType>* = nullptr>
LockedPtr(LockedPtr<SynchronizedType,LockPolicyType> && other)1349 LockedPtr(LockedPtr<SynchronizedType, LockPolicyType>&& other) noexcept
1350 : Base{std::move(other)} {}
1351
1352 /**
1353 * Move assignment operator.
1354 */
1355 LockedPtr& operator=(LockedPtr&& rhs) noexcept = default;
1356 template <
1357 typename LockPolicyType,
1358 EnableIfSameUnlockPolicy<LockPolicyType>* = nullptr>
1359 LockedPtr& operator=(
1360 LockedPtr<SynchronizedType, LockPolicyType>&& other) noexcept {
1361 Base::operator=(std::move(other));
1362 return *this;
1363 }
1364
1365 /*
1366 * Copy constructor and assignment operator are deleted.
1367 */
1368 LockedPtr(const LockedPtr& rhs) = delete;
1369 LockedPtr& operator=(const LockedPtr& rhs) = delete;
1370
1371 /**
1372 * Destructor releases.
1373 */
~LockedPtr()1374 ~LockedPtr() {}
1375
1376 /**
1377 * Check if this LockedPtr is uninitialized, or points to valid locked data.
1378 *
1379 * This method can be used to check if a timed-acquire operation succeeded.
1380 * If an acquire operation times out it will result in a null LockedPtr.
1381 *
1382 * A LockedPtr is always either null, or holds a lock to valid data.
1383 * Methods such as scopedUnlock() reset the LockedPtr to null for the
1384 * duration of the unlock.
1385 */
isNull()1386 bool isNull() const {
1387 return this->parent_ == nullptr;
1388 }
1389
1390 /**
1391 * Explicit boolean conversion.
1392 *
1393 * Returns !isNull()
1394 */
1395 explicit operator bool() const {
1396 return this->parent_ != nullptr;
1397 }
1398
1399 /**
1400 * Access the locked data.
1401 *
1402 * This method should only be used if the LockedPtr is valid.
1403 */
1404 CDataType* operator->() const {
1405 return &this->parent_->datum_;
1406 }
1407
1408 /**
1409 * Access the locked data.
1410 *
1411 * This method should only be used if the LockedPtr is valid.
1412 */
1413 CDataType& operator*() const {
1414 return this->parent_->datum_;
1415 }
1416
1417 /**
1418 * Temporarily unlock the LockedPtr, and reset it to null.
1419 *
1420 * Returns an helper object that will re-lock and restore the LockedPtr when
1421 * the helper is destroyed. The LockedPtr may not be dereferenced for as
1422 * long as this helper object exists.
1423 */
scopedUnlock()1424 ScopedUnlocker<SynchronizedType, LockPolicy> scopedUnlock() {
1425 return ScopedUnlocker<SynchronizedType, LockPolicy>(this);
1426 }
1427
1428 /***************************************************************************
1429 * Upgradable lock methods.
1430 * These are disabled via SFINAE when the mutex is not upgradable
1431 **************************************************************************/
1432 /**
1433 * Move the locked ptr from an upgrade state to an exclusive state. The
1434 * current lock is left in a null state.
1435 */
1436 template <
1437 typename SyncType = SynchronizedType,
1438 typename = typename std::enable_if<
1439 LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
1440 LockedPtr<SynchronizedType, LockPolicyFromUpgradeToExclusive>
moveFromUpgradeToWrite()1441 moveFromUpgradeToWrite() {
1442 return LockedPtr<SynchronizedType, LockPolicyFromUpgradeToExclusive>(
1443 exchange(this->parent_, nullptr));
1444 }
1445
1446 /**
1447 * Move the locked ptr from an exclusive state to an upgrade state. The
1448 * current lock is left in a null state.
1449 */
1450 template <
1451 typename SyncType = SynchronizedType,
1452 typename = typename std::enable_if<
1453 LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
1454 LockedPtr<SynchronizedType, LockPolicyFromExclusiveToUpgrade>
moveFromWriteToUpgrade()1455 moveFromWriteToUpgrade() {
1456 return LockedPtr<SynchronizedType, LockPolicyFromExclusiveToUpgrade>(
1457 exchange(this->parent_, nullptr));
1458 }
1459
1460 /**
1461 * Move the locked ptr from an upgrade state to a shared state. The
1462 * current lock is left in a null state.
1463 */
1464 template <
1465 typename SyncType = SynchronizedType,
1466 typename = typename std::enable_if<
1467 LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
1468 LockedPtr<SynchronizedType, LockPolicyFromUpgradeToShared>
moveFromUpgradeToRead()1469 moveFromUpgradeToRead() {
1470 return LockedPtr<SynchronizedType, LockPolicyFromUpgradeToShared>(
1471 exchange(this->parent_, nullptr));
1472 }
1473
1474 /**
1475 * Move the locked ptr from an exclusive state to a shared state. The
1476 * current lock is left in a null state.
1477 */
1478 template <
1479 typename SyncType = SynchronizedType,
1480 typename = typename std::enable_if<
1481 LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
1482 LockedPtr<SynchronizedType, LockPolicyFromExclusiveToShared>
moveFromWriteToRead()1483 moveFromWriteToRead() {
1484 return LockedPtr<SynchronizedType, LockPolicyFromExclusiveToShared>(
1485 exchange(this->parent_, nullptr));
1486 }
1487 };
1488
1489 /**
1490 * Helper functions that should be passed to either a lock() or synchronized()
1491 * invocation, these return implementation defined structs that will be used
1492 * to lock the synchronized instance appropriately.
1493 *
1494 * lock(wlock(one), rlock(two), wlock(three));
1495 * synchronized([](auto one, two) { ... }, wlock(one), rlock(two));
1496 *
1497 * For example in the above rlock() produces an implementation defined read
1498 * locking helper instance and wlock() a write locking helper
1499 *
1500 * Subsequent arguments passed to these locking helpers, after the first, will
1501 * be passed by const-ref to the corresponding function on the synchronized
1502 * instance. This means that if the function accepts these parameters by
1503 * value, they will be copied. Note that it is not necessary that the primary
1504 * locking function will be invoked at all (for eg. the implementation might
1505 * just invoke the try*Lock() method)
1506 *
1507 * // Try to acquire the lock for one second
1508 * synchronized([](auto) { ... }, wlock(one, 1s));
1509 *
1510 * // The timed lock acquire might never actually be called, if it is not
1511 * // needed by the underlying deadlock avoiding algorithm
1512 * synchronized([](auto, auto) { ... }, rlock(one), wlock(two, 1s));
1513 *
1514 * Note that the arguments passed to to *lock() calls will be passed by
1515 * const-ref to the function invocation, as the implementation might use them
1516 * many times
1517 */
1518 template <typename D, typename M, typename... Args>
wlock(Synchronized<D,M> & synchronized,Args &&...args)1519 auto wlock(Synchronized<D, M>& synchronized, Args&&... args) {
1520 return detail::wlock(synchronized, std::forward<Args>(args)...);
1521 }
1522 template <typename Data, typename Mutex, typename... Args>
rlock(const Synchronized<Data,Mutex> & synchronized,Args &&...args)1523 auto rlock(const Synchronized<Data, Mutex>& synchronized, Args&&... args) {
1524 return detail::rlock(synchronized, std::forward<Args>(args)...);
1525 }
1526 template <typename D, typename M, typename... Args>
ulock(Synchronized<D,M> & synchronized,Args &&...args)1527 auto ulock(Synchronized<D, M>& synchronized, Args&&... args) {
1528 return detail::ulock(synchronized, std::forward<Args>(args)...);
1529 }
1530 template <typename D, typename M, typename... Args>
lock(Synchronized<D,M> & synchronized,Args &&...args)1531 auto lock(Synchronized<D, M>& synchronized, Args&&... args) {
1532 return detail::lock(synchronized, std::forward<Args>(args)...);
1533 }
1534 template <typename D, typename M, typename... Args>
lock(const Synchronized<D,M> & synchronized,Args &&...args)1535 auto lock(const Synchronized<D, M>& synchronized, Args&&... args) {
1536 return detail::lock(synchronized, std::forward<Args>(args)...);
1537 }
1538
1539 /**
1540 * Acquire locks for multiple Synchronized<> objects, in a deadlock-safe
1541 * manner.
1542 *
1543 * Wrap the synchronized instances with the appropriate locking strategy by
1544 * using one of the four strategies - folly::lock (exclusive acquire for
1545 * exclusive only mutexes), folly::rlock (shared acquire for shareable
1546 * mutexes), folly::wlock (exclusive acquire for shareable mutexes) or
1547 * folly::ulock (upgrade acquire for upgrade mutexes) (see above)
1548 *
1549 * The locks will be acquired and the passed callable will be invoked with the
1550 * LockedPtr instances in the order that they were passed to the function
1551 */
1552 template <typename Func, typename... SynchronizedLockers>
decltype(auto)1553 decltype(auto) synchronized(Func&& func, SynchronizedLockers&&... lockers) {
1554 return apply(
1555 std::forward<Func>(func),
1556 lock(std::forward<SynchronizedLockers>(lockers)...));
1557 }
1558
1559 /**
1560 * Acquire locks on many lockables or synchronized instances in such a way
1561 * that the sequence of calls within the function does not cause deadlocks.
1562 *
1563 * This can often result in a performance boost as compared to simply
1564 * acquiring your locks in an ordered manner. Even for very simple cases.
1565 * The algorithm tried to adjust to contention by blocking on the mutex it
1566 * thinks is the best fit, leaving all other mutexes open to be locked by
1567 * other threads. See the benchmarks in folly/test/SynchronizedBenchmark.cpp
1568 * for more
1569 *
1570 * This works differently as compared to the locking algorithm in libstdc++
1571 * and is the recommended way to acquire mutexes in a generic order safe
1572 * manner. Performance benchmarks show that this does better than the one in
1573 * libstdc++ even for the simple cases
1574 *
1575 * Usage is the same as std::lock() for arbitrary lockables
1576 *
1577 * folly::lock(one, two, three);
1578 *
1579 * To make it work with folly::Synchronized you have to specify how you want
1580 * the locks to be acquired, use the folly::wlock(), folly::rlock(),
1581 * folly::ulock() and folly::lock() helpers defined below
1582 *
1583 * auto [one, two] = lock(folly::wlock(a), folly::rlock(b));
1584 *
1585 * Note that you can/must avoid the folly:: namespace prefix on the lock()
1586 * function if you use the helpers, ADL lookup is done to find the lock function
1587 *
1588 * This will execute the deadlock avoidance algorithm and acquire a write lock
1589 * for a and a read lock for b
1590 */
1591 template <typename LockableOne, typename LockableTwo, typename... Lockables>
lock(LockableOne & one,LockableTwo & two,Lockables &...lockables)1592 void lock(LockableOne& one, LockableTwo& two, Lockables&... lockables) {
1593 auto locker = [](auto& lockable) {
1594 using Lockable = std::remove_reference_t<decltype(lockable)>;
1595 return detail::makeSynchronizedLocker(
1596 lockable,
1597 [](auto& l) { return std::unique_lock<Lockable>{l}; },
1598 [](auto& l) {
1599 auto lock = std::unique_lock<Lockable>{l, std::defer_lock};
1600 lock.try_lock();
1601 return lock;
1602 });
1603 };
1604 auto locks = lock(locker(one), locker(two), locker(lockables)...);
1605
1606 // release ownership of the locks from the RAII lock wrapper returned by the
1607 // function above
1608 for_each(locks, [&](auto& lock) { lock.release(); });
1609 }
1610
1611 /**
1612 * Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
1613 * manner.
1614 *
1615 * The locks are acquired in order from lowest address to highest address.
1616 * (Note that this is not necessarily the same algorithm used by std::lock().)
1617 * For parameters that are const and support shared locks, a read lock is
1618 * acquired. Otherwise an exclusive lock is acquired.
1619 *
1620 * use lock() with folly::wlock(), folly::rlock() and folly::ulock() for
1621 * arbitrary locking without causing a deadlock (as much as possible), with the
1622 * same effects as std::lock()
1623 */
1624 template <class Sync1, class Sync2>
1625 std::tuple<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
acquireLocked(Sync1 & l1,Sync2 & l2)1626 acquireLocked(Sync1& l1, Sync2& l2) {
1627 if (static_cast<const void*>(&l1) < static_cast<const void*>(&l2)) {
1628 auto p1 = l1.contextualLock();
1629 auto p2 = l2.contextualLock();
1630 return std::make_tuple(std::move(p1), std::move(p2));
1631 } else {
1632 auto p2 = l2.contextualLock();
1633 auto p1 = l1.contextualLock();
1634 return std::make_tuple(std::move(p1), std::move(p2));
1635 }
1636 }
1637
1638 /**
1639 * A version of acquireLocked() that returns a std::pair rather than a
1640 * std::tuple, which is easier to use in many places.
1641 */
1642 template <class Sync1, class Sync2>
1643 std::pair<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
acquireLockedPair(Sync1 & l1,Sync2 & l2)1644 acquireLockedPair(Sync1& l1, Sync2& l2) {
1645 auto lockedPtrs = acquireLocked(l1, l2);
1646 return {std::move(std::get<0>(lockedPtrs)),
1647 std::move(std::get<1>(lockedPtrs))};
1648 }
1649
1650 /************************************************************************
1651 * NOTE: All APIs below this line will be deprecated in upcoming diffs.
1652 ************************************************************************/
1653
1654 // Non-member swap primitive
1655 template <class T, class M>
swap(Synchronized<T,M> & lhs,Synchronized<T,M> & rhs)1656 void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
1657 lhs.swap(rhs);
1658 }
1659
1660 /**
1661 * Disambiguate the name var by concatenating the line number of the original
1662 * point of expansion. This avoids shadowing warnings for nested
1663 * SYNCHRONIZEDs. The name is consistent if used multiple times within
1664 * another macro.
1665 * Only for internal use.
1666 */
1667 #define SYNCHRONIZED_VAR(var) FB_CONCATENATE(SYNCHRONIZED_##var##_, __LINE__)
1668
1669 /**
1670 * SYNCHRONIZED is the main facility that makes Synchronized<T>
1671 * helpful. It is a pseudo-statement that introduces a scope where the
1672 * object is locked. Inside that scope you get to access the unadorned
1673 * datum.
1674 *
1675 * Example:
1676 *
1677 * Synchronized<vector<int>> svector;
1678 * ...
1679 * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
1680 * or
1681 * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
1682 *
1683 * Refer to folly/docs/Synchronized.md for a detailed explanation and more
1684 * examples.
1685 */
1686 #define SYNCHRONIZED(...) \
1687 FOLLY_PUSH_WARNING \
1688 FOLLY_GNU_DISABLE_WARNING("-Wshadow") \
1689 FOLLY_MSVC_DISABLE_WARNING(4189) /* initialized but unreferenced */ \
1690 FOLLY_MSVC_DISABLE_WARNING(4456) /* declaration hides local */ \
1691 FOLLY_MSVC_DISABLE_WARNING(4457) /* declaration hides parameter */ \
1692 FOLLY_MSVC_DISABLE_WARNING(4458) /* declaration hides member */ \
1693 FOLLY_MSVC_DISABLE_WARNING(4459) /* declaration hides global */ \
1694 FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS \
1695 if (bool SYNCHRONIZED_VAR(state) = false) { \
1696 } else \
1697 for (auto SYNCHRONIZED_VAR(lockedPtr) = \
1698 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \
1699 !SYNCHRONIZED_VAR(state); \
1700 SYNCHRONIZED_VAR(state) = true) \
1701 for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
1702 *SYNCHRONIZED_VAR(lockedPtr).operator->(); \
1703 !SYNCHRONIZED_VAR(state); \
1704 SYNCHRONIZED_VAR(state) = true) \
1705 FOLLY_POP_WARNING
1706
1707 #define TIMED_SYNCHRONIZED(timeout, ...) \
1708 if (bool SYNCHRONIZED_VAR(state) = false) { \
1709 } else \
1710 for (auto SYNCHRONIZED_VAR(lockedPtr) = \
1711 (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \
1712 !SYNCHRONIZED_VAR(state); \
1713 SYNCHRONIZED_VAR(state) = true) \
1714 for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \
1715 (!SYNCHRONIZED_VAR(lockedPtr) \
1716 ? nullptr \
1717 : SYNCHRONIZED_VAR(lockedPtr).operator->()); \
1718 !SYNCHRONIZED_VAR(state); \
1719 SYNCHRONIZED_VAR(state) = true)
1720
1721 /**
1722 * Similar to SYNCHRONIZED, but only uses a read lock.
1723 */
1724 #define SYNCHRONIZED_CONST(...) \
1725 SYNCHRONIZED( \
1726 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
1727 as_const(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))))
1728
1729 /**
1730 * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
1731 */
1732 #define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
1733 TIMED_SYNCHRONIZED( \
1734 timeout, \
1735 FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
1736 as_const(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))))
1737
1738 /**
1739 * Synchronizes two Synchronized objects (they may encapsulate
1740 * different data). Synchronization is done in increasing address of
1741 * object order, so there is no deadlock risk.
1742 */
1743 #define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
1744 if (bool SYNCHRONIZED_VAR(state) = false) { \
1745 } else \
1746 for (auto SYNCHRONIZED_VAR(ptrs) = acquireLockedPair(e1, e2); \
1747 !SYNCHRONIZED_VAR(state); \
1748 SYNCHRONIZED_VAR(state) = true) \
1749 for (auto& n1 = *SYNCHRONIZED_VAR(ptrs).first; !SYNCHRONIZED_VAR(state); \
1750 SYNCHRONIZED_VAR(state) = true) \
1751 for (auto& n2 = *SYNCHRONIZED_VAR(ptrs).second; \
1752 !SYNCHRONIZED_VAR(state); \
1753 SYNCHRONIZED_VAR(state) = true)
1754
1755 } /* namespace folly */
1756