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