+template <class SynchronizedType, class LockPolicy>
+class ScopedUnlocker;
+
+/**
+ * A helper base class for implementing LockedPtr.
+ *
+ * The main reason for having this as a separate class is so we can specialize
+ * it for std::mutex, so we can expose a std::unique_lock to the caller
+ * when std::mutex is being used. This allows callers to use a
+ * std::condition_variable with the mutex from a Synchronized<T, std::mutex>.
+ *
+ * We don't use std::unique_lock with other Mutex types since it makes the
+ * LockedPtr class slightly larger, and it makes the logic to support
+ * ScopedUnlocker slightly more complicated. std::mutex is the only one that
+ * really seems to benefit from the unique_lock. std::condition_variable
+ * itself only supports std::unique_lock<std::mutex>, so there doesn't seem to
+ * be any real benefit to exposing the unique_lock with other mutex types.
+ *
+ * Note that the SynchronizedType template parameter may or may not be const
+ * qualified.
+ */
+template <class SynchronizedType, class Mutex, class LockPolicy>
+class LockedPtrBase {
+ public:
+ using MutexType = Mutex;
+ friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
+
+ /**
+ * Destructor releases.
+ */
+ ~LockedPtrBase() {
+ if (parent_) {
+ LockPolicy::unlock(parent_->mutex_);
+ }
+ }
+
+ protected:
+ LockedPtrBase() {}
+ explicit LockedPtrBase(SynchronizedType* parent) : parent_(parent) {
+ LockPolicy::lock(parent_->mutex_);
+ }
+ template <class Rep, class Period>
+ LockedPtrBase(
+ SynchronizedType* parent,
+ const std::chrono::duration<Rep, Period>& timeout) {
+ if (LockPolicy::try_lock_for(parent->mutex_, timeout)) {
+ this->parent_ = parent;
+ }
+ }
+ LockedPtrBase(LockedPtrBase&& rhs) noexcept : parent_(rhs.parent_) {
+ rhs.parent_ = nullptr;
+ }
+ LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
+ if (parent_) {
+ LockPolicy::unlock(parent_->mutex_);
+ }
+
+ parent_ = rhs.parent_;
+ rhs.parent_ = nullptr;
+ return *this;
+ }
+
+ using UnlockerData = SynchronizedType*;
+
+ /**
+ * Get a pointer to the Synchronized object from the UnlockerData.
+ *
+ * In the generic case UnlockerData is just the Synchronized pointer,
+ * so we return it as is. (This function is more interesting in the
+ * std::mutex specialization below.)
+ */
+ static SynchronizedType* getSynchronized(UnlockerData data) {
+ return data;
+ }
+
+ UnlockerData releaseLock() {
+ auto current = parent_;
+ parent_ = nullptr;
+ LockPolicy::unlock(current->mutex_);
+ return current;
+ }
+ void reacquireLock(UnlockerData&& data) {
+ DCHECK(parent_ == nullptr);
+ parent_ = data;
+ LockPolicy::lock(parent_->mutex_);
+ }
+
+ SynchronizedType* parent_ = nullptr;
+};
+
+/**
+ * LockedPtrBase specialization for use with std::mutex.
+ *
+ * When std::mutex is used we use a std::unique_lock to hold the mutex.
+ * This makes it possible to use std::condition_variable with a
+ * Synchronized<T, std::mutex>.
+ */
+template <class SynchronizedType, class LockPolicy>
+class LockedPtrBase<SynchronizedType, std::mutex, LockPolicy> {
+ public:
+ using MutexType = std::mutex;
+ friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>;
+
+ /**
+ * Destructor releases.
+ */
+ ~LockedPtrBase() {
+ // The std::unique_lock will automatically release the lock when it is
+ // destroyed, so we don't need to do anything extra here.
+ }
+
+ LockedPtrBase(LockedPtrBase&& rhs) noexcept
+ : lock_(std::move(rhs.lock_)), parent_(rhs.parent_) {
+ rhs.parent_ = nullptr;
+ }
+ LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept {
+ lock_ = std::move(rhs.lock_);
+ parent_ = rhs.parent_;
+ rhs.parent_ = nullptr;
+ return *this;
+ }
+
+ /**
+ * Get a reference to the std::unique_lock.
+ *
+ * This is provided so that callers can use Synchronized<T, std::mutex>
+ * with a std::condition_variable.
+ *
+ * While this API could be used to bypass the normal Synchronized APIs and
+ * manually interact with the underlying unique_lock, this is strongly
+ * discouraged.
+ */
+ std::unique_lock<std::mutex>& getUniqueLock() {
+ return lock_;
+ }
+
+ protected:
+ LockedPtrBase() {}
+ explicit LockedPtrBase(SynchronizedType* parent)
+ : lock_(parent->mutex_), parent_(parent) {}
+
+ using UnlockerData =
+ std::pair<std::unique_lock<std::mutex>, SynchronizedType*>;
+
+ static SynchronizedType* getSynchronized(const UnlockerData& data) {
+ return data.second;
+ }
+
+ UnlockerData releaseLock() {
+ UnlockerData data(std::move(lock_), parent_);
+ parent_ = nullptr;
+ data.first.unlock();
+ return data;
+ }
+ void reacquireLock(UnlockerData&& data) {
+ lock_ = std::move(data.first);
+ lock_.lock();
+ parent_ = data.second;
+ }
+
+ // The specialization for std::mutex does have to store slightly more
+ // state than the default implementation.
+ std::unique_lock<std::mutex> lock_;
+ SynchronizedType* parent_ = nullptr;
+};
+
+/**
+ * This class temporarily unlocks a LockedPtr in a scoped manner.
+ */
+template <class SynchronizedType, class LockPolicy>
+class ScopedUnlocker {
+ public:
+ explicit ScopedUnlocker(LockedPtr<SynchronizedType, LockPolicy>* p)
+ : ptr_(p), data_(ptr_->releaseLock()) {}
+ ScopedUnlocker(const ScopedUnlocker&) = delete;
+ ScopedUnlocker& operator=(const ScopedUnlocker&) = delete;
+ ScopedUnlocker(ScopedUnlocker&& other) noexcept
+ : ptr_(other.ptr_), data_(std::move(other.data_)) {
+ other.ptr_ = nullptr;
+ }
+ ScopedUnlocker& operator=(ScopedUnlocker&& other) = delete;
+
+ ~ScopedUnlocker() {
+ if (ptr_) {
+ ptr_->reacquireLock(std::move(data_));
+ }
+ }
+
+ /**
+ * Return a pointer to the Synchronized object used by this ScopedUnlocker.
+ */
+ SynchronizedType* getSynchronized() const {
+ return LockedPtr<SynchronizedType, LockPolicy>::getSynchronized(data_);
+ }
+
+ private:
+ using Data = typename LockedPtr<SynchronizedType, LockPolicy>::UnlockerData;
+ LockedPtr<SynchronizedType, LockPolicy>* ptr_{nullptr};
+ Data data_;
+};
+
+/**
+ * A LockedPtr keeps a Synchronized<T> object locked for the duration of
+ * LockedPtr's existence.
+ *
+ * It provides access the datum's members directly by using operator->() and
+ * operator*().
+ *
+ * The LockPolicy parameter controls whether or not the lock is acquired in
+ * exclusive or shared mode.
+ */
+template <class SynchronizedType, class LockPolicy>
+class LockedPtr : public LockedPtrBase<
+ SynchronizedType,
+ typename SynchronizedType::MutexType,
+ LockPolicy> {
+ private:
+ using Base = LockedPtrBase<
+ SynchronizedType,
+ typename SynchronizedType::MutexType,
+ LockPolicy>;
+ using UnlockerData = typename Base::UnlockerData;
+ // CDataType is the DataType with the appropriate const-qualification
+ using CDataType = typename std::conditional<
+ std::is_const<SynchronizedType>::value,
+ typename SynchronizedType::DataType const,
+ typename SynchronizedType::DataType>::type;
+
+ public:
+ using DataType = typename SynchronizedType::DataType;
+ using MutexType = typename SynchronizedType::MutexType;
+ using Synchronized = typename std::remove_const<SynchronizedType>::type;
+ friend class ScopedUnlocker<SynchronizedType, LockPolicy>;
+
+ /**
+ * Creates an uninitialized LockedPtr.
+ *
+ * Dereferencing an uninitialized LockedPtr is not allowed.
+ */
+ LockedPtr() {}
+
+ /**
+ * Takes a Synchronized<T> and locks it.
+ */
+ explicit LockedPtr(SynchronizedType* parent) : Base(parent) {}
+
+ /**
+ * Takes a Synchronized<T> and attempts to lock it, within the specified
+ * timeout.
+ *
+ * Blocks until the lock is acquired or until the specified timeout expires.
+ * If the timeout expired without acquiring the lock, the LockedPtr will be
+ * null, and LockedPtr::isNull() will return true.
+ */
+ template <class Rep, class Period>
+ LockedPtr(
+ SynchronizedType* parent,
+ const std::chrono::duration<Rep, Period>& timeout)
+ : Base(parent, timeout) {}
+
+ /**
+ * Move constructor.
+ */
+ LockedPtr(LockedPtr&& rhs) noexcept = default;
+
+ /**
+ * Move assignment operator.
+ */
+ LockedPtr& operator=(LockedPtr&& rhs) noexcept = default;
+
+ /*
+ * Copy constructor and assignment operator are deleted.
+ */
+ LockedPtr(const LockedPtr& rhs) = delete;
+ LockedPtr& operator=(const LockedPtr& rhs) = delete;
+
+ /**
+ * Destructor releases.
+ */
+ ~LockedPtr() {}
+
+ /**
+ * Check if this LockedPtr is uninitialized, or points to valid locked data.
+ *
+ * This method can be used to check if a timed-acquire operation succeeded.
+ * If an acquire operation times out it will result in a null LockedPtr.
+ *
+ * A LockedPtr is always either null, or holds a lock to valid data.
+ * Methods such as scopedUnlock() reset the LockedPtr to null for the
+ * duration of the unlock.
+ */
+ bool isNull() const {
+ return this->parent_ == nullptr;
+ }
+
+ /**
+ * Explicit boolean conversion.
+ *
+ * Returns !isNull()
+ */
+ explicit operator bool() const {
+ return this->parent_ != nullptr;
+ }
+
+ /**
+ * Access the locked data.
+ *
+ * This method should only be used if the LockedPtr is valid.
+ */
+ CDataType* operator->() const {
+ return &this->parent_->datum_;
+ }
+
+ /**
+ * Access the locked data.
+ *
+ * This method should only be used if the LockedPtr is valid.
+ */
+ CDataType& operator*() const {
+ return this->parent_->datum_;
+ }
+
+ /**
+ * Temporarily unlock the LockedPtr, and reset it to null.
+ *
+ * Returns an helper object that will re-lock and restore the LockedPtr when
+ * the helper is destroyed. The LockedPtr may not be dereferenced for as
+ * long as this helper object exists.
+ */
+ ScopedUnlocker<SynchronizedType, LockPolicy> scopedUnlock() {
+ return ScopedUnlocker<SynchronizedType, LockPolicy>(this);
+ }
+};
+
+namespace detail {
+/*
+ * A helper alias to select a ConstLockedPtr if the template parameter is a
+ * const Synchronized<T>, or a LockedPtr if the parameter is not const.
+ */
+template <class SynchronizedType>
+using LockedPtrType = typename std::conditional<
+ std::is_const<SynchronizedType>::value,
+ typename SynchronizedType::ConstLockedPtr,
+ typename SynchronizedType::LockedPtr>::type;
+} // detail
+
+/**
+ * Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
+ * manner.
+ *
+ * The locks are acquired in order from lowest address to highest address.
+ * (Note that this is not necessarily the same algorithm used by std::lock().)
+ *
+ * For parameters that are const and support shared locks, a read lock is
+ * acquired. Otherwise an exclusive lock is acquired.
+ *
+ * TODO: Extend acquireLocked() with variadic template versions that
+ * allow for more than 2 Synchronized arguments. (I haven't given too much
+ * thought about how to implement this. It seems like it would be rather
+ * complicated, but I think it should be possible.)
+ */
+template <class Sync1, class Sync2>
+std::tuple<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
+acquireLocked(Sync1& l1, Sync2& l2) {
+ if (static_cast<const void*>(&l1) < static_cast<const void*>(&l2)) {
+ auto p1 = l1.contextualLock();
+ auto p2 = l2.contextualLock();
+ return std::make_tuple(std::move(p1), std::move(p2));
+ } else {
+ auto p2 = l2.contextualLock();
+ auto p1 = l1.contextualLock();
+ return std::make_tuple(std::move(p1), std::move(p2));
+ }
+}
+
+/**
+ * A version of acquireLocked() that returns a std::pair rather than a
+ * std::tuple, which is easier to use in many places.
+ */
+template <class Sync1, class Sync2>
+std::pair<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>>
+acquireLockedPair(Sync1& l1, Sync2& l2) {
+ auto lockedPtrs = acquireLocked(l1, l2);
+ return {std::move(std::get<0>(lockedPtrs)),
+ std::move(std::get<1>(lockedPtrs))};
+}
+
+/************************************************************************
+ * NOTE: All APIs below this line will be deprecated in upcoming diffs.
+ ************************************************************************/
+