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