/*
 * Copyright 2016 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * This module implements a Synchronized abstraction useful in
 * mutex-based concurrency.
 *
 * The Synchronized<T, Mutex> class is the primary public API exposed by this
 * module.  See folly/docs/Synchronized.md for a more complete explanation of
 * this class and its benefits.
 */

#pragma once

#include <folly/Likely.h>
#include <folly/LockTraits.h>
#include <folly/Preprocessor.h>
#include <folly/SharedMutex.h>
#include <folly/Traits.h>
#include <glog/logging.h>
#include <mutex>
#include <type_traits>

namespace folly {

template <class LockedType, class Mutex, class LockPolicy>
class LockedPtrBase;
template <class LockedType, class LockPolicy>
class LockedPtr;
template <class LockedType, class LockPolicy = LockPolicyExclusive>
class LockedGuardPtr;

/**
 * Public version of LockInterfaceDispatcher that contains the MutexLevel enum
 * for the passed in mutex type
 *
 * This is decoupled from MutexLevelValueImpl in LockTraits.h because this
 * ensures that a heterogenous mutex with a different API can be used.  For
 * example - if a mutex does not have a lock_shared() method but the
 * LockTraits specialization for it supports a static non member
 * lock_shared(Mutex&) it can be used as a shared mutex and will provide
 * rlock() and wlock() functions.
 */
template <class Mutex>
using MutexLevelValue = detail::MutexLevelValueImpl<
    true,
    LockTraits<Mutex>::is_shared,
    LockTraits<Mutex>::is_upgrade>;

/**
 * SynchronizedBase is a helper parent class for Synchronized<T>.
 *
 * It provides wlock() and rlock() methods for shared mutex types,
 * or lock() methods for purely exclusive mutex types.
 */
template <class Subclass, detail::MutexLevel level>
class SynchronizedBase;

/**
 * SynchronizedBase specialization for shared mutex types.
 *
 * This class provides wlock() and rlock() methods for acquiring the lock and
 * accessing the data.
 */
template <class Subclass>
class SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
 public:
  using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
  using ConstWLockedPtr =
      ::folly::LockedPtr<const Subclass, LockPolicyExclusive>;
  using ConstLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyShared>;

  /**
   * Acquire an exclusive lock, and return a LockedPtr that can be used to
   * safely access the datum.
   *
   * LockedPtr offers operator -> and * to provide access to the datum.
   * The lock will be released when the LockedPtr is destroyed.
   */
  LockedPtr wlock() {
    return LockedPtr(static_cast<Subclass*>(this));
  }
  ConstWLockedPtr wlock() const {
    return ConstWLockedPtr(static_cast<const Subclass*>(this));
  }

  /**
   * Acquire a read lock, and return a ConstLockedPtr that can be used to
   * safely access the datum.
   */
  ConstLockedPtr rlock() const {
    return ConstLockedPtr(static_cast<const Subclass*>(this));
  }

  /**
   * Attempts to acquire the lock, or fails if the timeout elapses first.
   * If acquisition is unsuccessful, the returned LockedPtr will be null.
   *
   * (Use LockedPtr::isNull() to check for validity.)
   */
  template <class Rep, class Period>
  LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) {
    return LockedPtr(static_cast<Subclass*>(this), timeout);
  }
  template <class Rep, class Period>
  ConstWLockedPtr wlock(
      const std::chrono::duration<Rep, Period>& timeout) const {
    return ConstWLockedPtr(static_cast<const Subclass*>(this), timeout);
  }

  /**
   * Attempts to acquire the lock, or fails if the timeout elapses first.
   * If acquisition is unsuccessful, the returned LockedPtr will be null.
   *
   * (Use LockedPtr::isNull() to check for validity.)
   */
  template <class Rep, class Period>
  ConstLockedPtr rlock(
      const std::chrono::duration<Rep, Period>& timeout) const {
    return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
  }

  /*
   * Note: C++ 17 adds guaranteed copy elision.  (http://wg21.link/P0135)
   * Once compilers support this, it would be nice to add wguard() and rguard()
   * methods that return LockedGuardPtr objects.
   */

  /**
   * Invoke a function while holding the lock exclusively.
   *
   * A reference to the datum will be passed into the function as its only
   * argument.
   *
   * This can be used with a lambda argument for easily defining small critical
   * sections in the code.  For example:
   *
   *   auto value = obj.withWLock([](auto& data) {
   *     data.doStuff();
   *     return data.getValue();
   *   });
   */
  template <class Function>
  auto withWLock(Function&& function) {
    LockedGuardPtr<Subclass, LockPolicyExclusive> guardPtr(
        static_cast<Subclass*>(this));
    return function(*guardPtr);
  }
  template <class Function>
  auto withWLock(Function&& function) const {
    LockedGuardPtr<const Subclass, LockPolicyExclusive> guardPtr(
        static_cast<const Subclass*>(this));
    return function(*guardPtr);
  }

  /**
   * Invoke a function while holding the lock exclusively.
   *
   * This is similar to withWLock(), but the function will be passed a
   * LockedPtr rather than a reference to the data itself.
   *
   * This allows scopedUnlock() to be called on the LockedPtr argument if
   * desired.
   */
  template <class Function>
  auto withWLockPtr(Function&& function) {
    return function(wlock());
  }
  template <class Function>
  auto withWLockPtr(Function&& function) const {
    return function(wlock());
  }

  /**
   * Invoke a function while holding an the lock in shared mode.
   *
   * A const reference to the datum will be passed into the function as its
   * only argument.
   */
  template <class Function>
  auto withRLock(Function&& function) const {
    LockedGuardPtr<const Subclass, LockPolicyShared> guardPtr(
        static_cast<const Subclass*>(this));
    return function(*guardPtr);
  }

  template <class Function>
  auto withRLockPtr(Function&& function) const {
    return function(rlock());
  }
};

/**
 * SynchronizedBase specialization for upgrade mutex types.
 *
 * This class provides all the functionality provided by the SynchronizedBase
 * specialization for shared mutexes and a ulock() method that returns an
 * upgradable lock RAII proxy
 */
template <class Subclass>
class SynchronizedBase<Subclass, detail::MutexLevel::UPGRADE>
    : public SynchronizedBase<Subclass, detail::MutexLevel::SHARED> {
 public:
  using UpgradeLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyUpgrade>;
  using ConstUpgradeLockedPtr =
      ::folly::LockedPtr<const Subclass, LockPolicyUpgrade>;
  using UpgradeLockedGuardPtr =
      ::folly::LockedGuardPtr<Subclass, LockPolicyUpgrade>;
  using ConstUpgradeLockedGuardPtr =
      ::folly::LockedGuardPtr<const Subclass, LockPolicyUpgrade>;

  /**
   * Acquire an upgrade lock and return a LockedPtr that can be used to safely
   * access the datum
   *
   * And the const version
   */
  UpgradeLockedPtr ulock() {
    return UpgradeLockedPtr(static_cast<Subclass*>(this));
  }
  ConstUpgradeLockedPtr ulock() const {
    return ConstUpgradeLockedPtr(static_cast<const Subclass*>(this));
  }

  /**
   * Acquire an upgrade lock and return a LockedPtr that can be used to safely
   * access the datum
   *
   * And the const version
   */
  template <class Rep, class Period>
  UpgradeLockedPtr ulock(const std::chrono::duration<Rep, Period>& timeout) {
    return UpgradeLockedPtr(static_cast<Subclass*>(this), timeout);
  }
  template <class Rep, class Period>
  UpgradeLockedPtr ulock(
      const std::chrono::duration<Rep, Period>& timeout) const {
    return ConstUpgradeLockedPtr(static_cast<const Subclass*>(this), timeout);
  }

  /**
   * Invoke a function while holding the lock.
   *
   * A reference to the datum will be passed into the function as its only
   * argument.
   *
   * This can be used with a lambda argument for easily defining small critical
   * sections in the code.  For example:
   *
   *   auto value = obj.withULock([](auto& data) {
   *     data.doStuff();
   *     return data.getValue();
   *   });
   *
   * This is probably not the function you want.  If the intent is to read the
   * data object and determine whether you should upgrade to a write lock then
   * the withULockPtr() method should be called instead, since it gives access
   * to the LockedPtr proxy (which can be upgraded via the
   * moveFromUpgradeToWrite() method)
   */
  template <class Function>
  auto withULock(Function&& function) const {
    ConstUpgradeLockedGuardPtr guardPtr(static_cast<const Subclass*>(this));
    return function(*guardPtr);
  }

  /**
   * Invoke a function while holding the lock exclusively.
   *
   * This is similar to withULock(), but the function will be passed a
   * LockedPtr rather than a reference to the data itself.
   *
   * This allows scopedUnlock() and getUniqueLock() to be called on the
   * LockedPtr argument.
   *
   * This also allows you to upgrade the LockedPtr proxy to a write state so
   * that changes can be made to the underlying data
   */
  template <class Function>
  auto withULockPtr(Function&& function) {
    return function(ulock());
  }
  template <class Function>
  auto withULockPtr(Function&& function) const {
    return function(ulock());
  }
};

/**
 * SynchronizedBase specialization for non-shared mutex types.
 *
 * This class provides lock() methods for acquiring the lock and accessing the
 * data.
 */
template <class Subclass>
class SynchronizedBase<Subclass, detail::MutexLevel::UNIQUE> {
 public:
  using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>;
  using ConstLockedPtr =
      ::folly::LockedPtr<const Subclass, LockPolicyExclusive>;

  /**
   * Acquire a lock, and return a LockedPtr that can be used to safely access
   * the datum.
   */
  LockedPtr lock() {
    return LockedPtr(static_cast<Subclass*>(this));
  }

  /**
   * Acquire a lock, and return a ConstLockedPtr that can be used to safely
   * access the datum.
   */
  ConstLockedPtr lock() const {
    return ConstLockedPtr(static_cast<const Subclass*>(this));
  }

  /**
   * Attempts to acquire the lock, or fails if the timeout elapses first.
   * If acquisition is unsuccessful, the returned LockedPtr will be null.
   */
  template <class Rep, class Period>
  LockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) {
    return LockedPtr(static_cast<Subclass*>(this), timeout);
  }

  /**
   * Attempts to acquire the lock, or fails if the timeout elapses first.
   * If acquisition is unsuccessful, the returned LockedPtr will be null.
   */
  template <class Rep, class Period>
  ConstLockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) const {
    return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
  }

  /*
   * Note: C++ 17 adds guaranteed copy elision.  (http://wg21.link/P0135)
   * Once compilers support this, it would be nice to add guard() methods that
   * return LockedGuardPtr objects.
   */

  /**
   * Invoke a function while holding the lock.
   *
   * A reference to the datum will be passed into the function as its only
   * argument.
   *
   * This can be used with a lambda argument for easily defining small critical
   * sections in the code.  For example:
   *
   *   auto value = obj.withLock([](auto& data) {
   *     data.doStuff();
   *     return data.getValue();
   *   });
   */
  template <class Function>
  auto withLock(Function&& function) {
    LockedGuardPtr<Subclass, LockPolicyExclusive> guardPtr(
        static_cast<Subclass*>(this));
    return function(*guardPtr);
  }
  template <class Function>
  auto withLock(Function&& function) const {
    LockedGuardPtr<const Subclass, LockPolicyExclusive> guardPtr(
        static_cast<const Subclass*>(this));
    return function(*guardPtr);
  }

  /**
   * Invoke a function while holding the lock exclusively.
   *
   * This is similar to withWLock(), but the function will be passed a
   * LockedPtr rather than a reference to the data itself.
   *
   * This allows scopedUnlock() and getUniqueLock() to be called on the
   * LockedPtr argument.
   */
  template <class Function>
  auto withLockPtr(Function&& function) {
    return function(lock());
  }
  template <class Function>
  auto withLockPtr(Function&& function) const {
    return function(lock());
  }
};

/**
 * Synchronized<T> encapsulates an object of type T (a "datum") paired
 * with a mutex. The only way to access the datum is while the mutex
 * is locked, and Synchronized makes it virtually impossible to do
 * otherwise. The code that would access the datum in unsafe ways
 * would look odd and convoluted, thus readily alerting the human
 * reviewer. In contrast, the code that uses Synchronized<T> correctly
 * looks simple and intuitive.
 *
 * The second parameter must be a mutex type.  Any mutex type supported by
 * LockTraits<Mutex> can be used.  By default any class with lock() and
 * unlock() methods will work automatically.  LockTraits can be specialized to
 * teach Synchronized how to use other custom mutex types.  See the
 * documentation in LockTraits.h for additional details.
 *
 * Supported mutexes that work by default include std::mutex,
 * std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex,
 * folly::SharedMutex, folly::RWSpinLock, and folly::SpinLock.
 * Include LockTraitsBoost.h to get additional LockTraits specializations to
 * support the following boost mutex types: boost::mutex,
 * boost::recursive_mutex, boost::shared_mutex, boost::timed_mutex, and
 * boost::recursive_timed_mutex.
 */
template <class T, class Mutex = SharedMutex>
struct Synchronized : public SynchronizedBase<
                          Synchronized<T, Mutex>,
                          MutexLevelValue<Mutex>::value> {
 private:
  using Base =
      SynchronizedBase<Synchronized<T, Mutex>, MutexLevelValue<Mutex>::value>;
  static constexpr bool nxCopyCtor{
      std::is_nothrow_copy_constructible<T>::value};
  static constexpr bool nxMoveCtor{
      std::is_nothrow_move_constructible<T>::value};

 public:
  using LockedPtr = typename Base::LockedPtr;
  using ConstLockedPtr = typename Base::ConstLockedPtr;
  using DataType = T;
  using MutexType = Mutex;

  /**
   * Default constructor leaves both members call their own default
   * constructor.
   */
  Synchronized() = default;

  /**
   * Copy constructor copies the data (with locking the source and
   * all) but does NOT copy the mutex. Doing so would result in
   * deadlocks.
   *
   * Note that the copy constructor may throw because it acquires a lock in
   * the contextualRLock() method
   */
  Synchronized(const Synchronized& rhs) /* may throw */
      : Synchronized(rhs, rhs.contextualRLock()) {}

  /**
   * Move constructor moves the data (with locking the source and all)
   * but does not move the mutex.
   *
   * Note that the move constructor may throw because it acquires a lock.
   * Since the move constructor is not declared noexcept, when objects of this
   * class are used as elements in a vector or a similar container.  The
   * elements might not be moved around when resizing.  They might be copied
   * instead.  You have been warned.
   */
  Synchronized(Synchronized&& rhs) /* may throw */
      : Synchronized(std::move(rhs), rhs.contextualLock()) {}

  /**
   * Constructor taking a datum as argument copies it. There is no
   * need to lock the constructing object.
   */
  explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}

  /**
   * Constructor taking a datum rvalue as argument moves it. Again,
   * there is no need to lock the constructing object.
   */
  explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
      : datum_(std::move(rhs)) {}

  /**
   * Lets you construct non-movable types in-place. Use the constexpr
   * instance `construct_in_place` as the first argument.
   */
  template <typename... Args>
  explicit Synchronized(construct_in_place_t, Args&&... args)
      : datum_(std::forward<Args>(args)...) {}

  /**
   * The canonical assignment operator only assigns the data, NOT the
   * mutex. It locks the two objects in ascending order of their
   * addresses.
   */
  Synchronized& operator=(const Synchronized& rhs) {
    if (this == &rhs) {
      // Self-assignment, pass.
    } else if (this < &rhs) {
      auto guard1 = operator->();
      auto guard2 = rhs.operator->();
      datum_ = rhs.datum_;
    } else {
      auto guard1 = rhs.operator->();
      auto guard2 = operator->();
      datum_ = rhs.datum_;
    }
    return *this;
  }

  /**
   * Move assignment operator, only assigns the data, NOT the
   * mutex. It locks the two objects in ascending order of their
   * addresses.
   */
  Synchronized& operator=(Synchronized&& rhs) {
    if (this == &rhs) {
      // Self-assignment, pass.
    } else if (this < &rhs) {
      auto guard1 = operator->();
      auto guard2 = rhs.operator->();
      datum_ = std::move(rhs.datum_);
    } else {
      auto guard1 = rhs.operator->();
      auto guard2 = operator->();
      datum_ = std::move(rhs.datum_);
    }
    return *this;
  }

  /**
   * Lock object, assign datum.
   */
  Synchronized& operator=(const T& rhs) {
    auto guard = operator->();
    datum_ = rhs;
    return *this;
  }

  /**
   * Lock object, move-assign datum.
   */
  Synchronized& operator=(T&& rhs) {
    auto guard = operator->();
    datum_ = std::move(rhs);
    return *this;
  }

  /**
   * Acquire an appropriate lock based on the context.
   *
   * If the mutex is a shared mutex, and the Synchronized instance is const,
   * this acquires a shared lock.  Otherwise this acquires an exclusive lock.
   *
   * In general, prefer using the explicit rlock() and wlock() methods
   * for read-write locks, and lock() for purely exclusive locks.
   *
   * contextualLock() is primarily intended for use in other template functions
   * that do not necessarily know the lock type.
   */
  LockedPtr contextualLock() {
    return LockedPtr(this);
  }
  ConstLockedPtr contextualLock() const {
    return ConstLockedPtr(this);
  }
  template <class Rep, class Period>
  LockedPtr contextualLock(const std::chrono::duration<Rep, Period>& timeout) {
    return LockedPtr(this, timeout);
  }
  template <class Rep, class Period>
  ConstLockedPtr contextualLock(
      const std::chrono::duration<Rep, Period>& timeout) const {
    return ConstLockedPtr(this, timeout);
  }
  /**
   * contextualRLock() acquires a read lock if the mutex type is shared,
   * or a regular exclusive lock for non-shared mutex types.
   *
   * contextualRLock() when you know that you prefer a read lock (if
   * available), even if the Synchronized<T> object itself is non-const.
   */
  ConstLockedPtr contextualRLock() const {
    return ConstLockedPtr(this);
  }
  template <class Rep, class Period>
  ConstLockedPtr contextualRLock(
      const std::chrono::duration<Rep, Period>& timeout) const {
    return ConstLockedPtr(this, timeout);
  }

  /**
   * This accessor offers a LockedPtr. In turn, LockedPtr offers
   * operator-> returning a pointer to T. The operator-> keeps
   * expanding until it reaches a pointer, so syncobj->foo() will lock
   * the object and call foo() against it.
   *
   * NOTE: This API is planned to be deprecated in an upcoming diff.
   * Prefer using lock(), wlock(), or rlock() instead.
   */
  LockedPtr operator->() {
    return LockedPtr(this);
  }

  /**
   * Obtain a ConstLockedPtr.
   *
   * NOTE: This API is planned to be deprecated in an upcoming diff.
   * Prefer using lock(), wlock(), or rlock() instead.
   */
  ConstLockedPtr operator->() const {
    return ConstLockedPtr(this);
  }

  /**
   * Attempts to acquire for a given number of milliseconds. If
   * acquisition is unsuccessful, the returned LockedPtr is NULL.
   *
   * NOTE: This API is deprecated.  Use lock(), wlock(), or rlock() instead.
   * In the future it will be marked with a deprecation attribute to emit
   * build-time warnings, and then it will be removed entirely.
   */
  LockedPtr timedAcquire(unsigned int milliseconds) {
    return LockedPtr(this, std::chrono::milliseconds(milliseconds));
  }

  /**
   * Attempts to acquire for a given number of milliseconds. If
   * acquisition is unsuccessful, the returned ConstLockedPtr is NULL.
   *
   * NOTE: This API is deprecated.  Use lock(), wlock(), or rlock() instead.
   * In the future it will be marked with a deprecation attribute to emit
   * build-time warnings, and then it will be removed entirely.
   */
  ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
    return ConstLockedPtr(this, std::chrono::milliseconds(milliseconds));
  }

  /**
   * Sometimes, although you have a mutable object, you only want to
   * call a const method against it. The most efficient way to achieve
   * that is by using a read lock. You get to do so by using
   * obj.asConst()->method() instead of obj->method().
   *
   * NOTE: This API is planned to be deprecated in an upcoming diff.
   * Use rlock() instead.
   */
  const Synchronized& asConst() const {
    return *this;
  }

  /**
   * Swaps with another Synchronized. Protected against
   * self-swap. Only data is swapped. Locks are acquired in increasing
   * address order.
   */
  void swap(Synchronized& rhs) {
    if (this == &rhs) {
      return;
    }
    if (this > &rhs) {
      return rhs.swap(*this);
    }
    auto guard1 = operator->();
    auto guard2 = rhs.operator->();

    using std::swap;
    swap(datum_, rhs.datum_);
  }

  /**
   * Swap with another datum. Recommended because it keeps the mutex
   * held only briefly.
   */
  void swap(T& rhs) {
    LockedPtr guard(this);

    using std::swap;
    swap(datum_, rhs);
  }

  /**
   * Copies datum to a given target.
   */
  void copy(T* target) const {
    ConstLockedPtr guard(this);
    *target = datum_;
  }

  /**
   * Returns a fresh copy of the datum.
   */
  T copy() const {
    ConstLockedPtr guard(this);
    return datum_;
  }

 private:
  template <class LockedType, class MutexType, class LockPolicy>
  friend class folly::LockedPtrBase;
  template <class LockedType, class LockPolicy>
  friend class folly::LockedPtr;
  template <class LockedType, class LockPolicy>
  friend class folly::LockedGuardPtr;

  /**
   * Helper constructors to enable Synchronized for
   * non-default constructible types T.
   * Guards are created in actual public constructors and are alive
   * for the time required to construct the object
   */
  Synchronized(
      const Synchronized& rhs,
      const ConstLockedPtr& /*guard*/) noexcept(nxCopyCtor)
      : datum_(rhs.datum_) {}

  Synchronized(Synchronized&& rhs, const LockedPtr& /*guard*/) noexcept(
      nxMoveCtor)
      : datum_(std::move(rhs.datum_)) {}

  // Synchronized data members
  T datum_;
  mutable Mutex mutex_;
};

template <class SynchronizedType, class LockPolicy>
class ScopedUnlocker;

namespace detail {
/*
 * A helper alias that resolves to "const T" if the template parameter
 * is a const Synchronized<T>, or "T" if the parameter is not const.
 */
template <class SynchronizedType>
using SynchronizedDataType = typename std::conditional<
    std::is_const<SynchronizedType>::value,
    typename SynchronizedType::DataType const,
    typename SynchronizedType::DataType>::type;
/*
 * A helper alias that resolves to 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

/**
 * 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_);
    }
  }

  /**
   * Unlock the synchronized data.
   *
   * The LockedPtr can no longer be dereferenced after unlock() has been
   * called.  isValid() will return false on an unlocked LockedPtr.
   *
   * unlock() can only be called on a LockedPtr that is valid.
   */
  void unlock() {
    DCHECK(parent_ != nullptr);
    LockPolicy::unlock(parent_->mutex_);
    parent_ = nullptr;
  }

 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() {
    DCHECK(parent_ != nullptr);
    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_;
  }

  /**
   * Unlock the synchronized data.
   *
   * The LockedPtr can no longer be dereferenced after unlock() has been
   * called.  isValid() will return false on an unlocked LockedPtr.
   *
   * unlock() can only be called on a LockedPtr that is valid.
   */
  void unlock() {
    DCHECK(parent_ != nullptr);
    lock_.unlock();
    parent_ = nullptr;
  }

 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() {
    DCHECK(parent_ != nullptr);
    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 = detail::SynchronizedDataType<SynchronizedType>;

 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);
  }

  /***************************************************************************
   * Upgradable lock methods.
   * These are disabled via SFINAE when the mutex is not upgradable
   **************************************************************************/
  /**
   * Move the locked ptr from an upgrade state to an exclusive state.  The
   * current lock is left in a null state.
   */
  template <
      typename SyncType = SynchronizedType,
      typename = typename std::enable_if<
          LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
  LockedPtr<SynchronizedType, LockPolicyFromUpgradeToExclusive>
  moveFromUpgradeToWrite() {
    auto* parent_to_pass_on = this->parent_;
    this->parent_ = nullptr;
    return LockedPtr<SynchronizedType, LockPolicyFromUpgradeToExclusive>(
        parent_to_pass_on);
  }

  /**
   * Move the locked ptr from an exclusive state to an upgrade state.  The
   * current lock is left in a null state.
   */
  template <
      typename SyncType = SynchronizedType,
      typename = typename std::enable_if<
          LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
  LockedPtr<SynchronizedType, LockPolicyFromExclusiveToUpgrade>
  moveFromWriteToUpgrade() {
    auto* parent_to_pass_on = this->parent_;
    this->parent_ = nullptr;
    return LockedPtr<SynchronizedType, LockPolicyFromExclusiveToUpgrade>(
        parent_to_pass_on);
  }

  /**
   * Move the locked ptr from an upgrade state to a shared state.  The
   * current lock is left in a null state.
   */
  template <
      typename SyncType = SynchronizedType,
      typename = typename std::enable_if<
          LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
  LockedPtr<SynchronizedType, LockPolicyFromUpgradeToShared>
  moveFromUpgradeToRead() {
    auto* parent_to_pass_on = this->parent_;
    this->parent_ = nullptr;
    return LockedPtr<SynchronizedType, LockPolicyFromUpgradeToShared>(
        parent_to_pass_on);
  }

  /**
   * Move the locked ptr from an exclusive state to a shared state.  The
   * current lock is left in a null state.
   */
  template <
      typename SyncType = SynchronizedType,
      typename = typename std::enable_if<
          LockTraits<typename SyncType::MutexType>::is_upgrade>::type>
  LockedPtr<SynchronizedType, LockPolicyFromExclusiveToShared>
  moveFromWriteToRead() {
    auto* parent_to_pass_on = this->parent_;
    this->parent_ = nullptr;
    return LockedPtr<SynchronizedType, LockPolicyFromExclusiveToShared>(
        parent_to_pass_on);
  }
};

/**
 * LockedGuardPtr is a simplified version of LockedPtr.
 *
 * It is non-movable, and supports fewer features than LockedPtr.  However, it
 * is ever-so-slightly more performant than LockedPtr.  (The destructor can
 * unconditionally release the lock, without requiring a conditional branch.)
 *
 * The relationship between LockedGuardPtr and LockedPtr is similar to that
 * between std::lock_guard and std::unique_lock.
 */
template <class SynchronizedType, class LockPolicy>
class LockedGuardPtr {
 private:
  // CDataType is the DataType with the appropriate const-qualification
  using CDataType = detail::SynchronizedDataType<SynchronizedType>;

 public:
  using DataType = typename SynchronizedType::DataType;
  using MutexType = typename SynchronizedType::MutexType;
  using Synchronized = typename std::remove_const<SynchronizedType>::type;

  LockedGuardPtr() = delete;

  /**
   * Takes a Synchronized<T> and locks it.
   */
  explicit LockedGuardPtr(SynchronizedType* parent) : parent_(parent) {
    LockPolicy::lock(parent_->mutex_);
  }

  /**
   * Destructor releases.
   */
  ~LockedGuardPtr() {
    LockPolicy::unlock(parent_->mutex_);
  }

  /**
   * Access the locked data.
   */
  CDataType* operator->() const {
    return &parent_->datum_;
  }

  /**
   * Access the locked data.
   */
  CDataType& operator*() const {
    return parent_->datum_;
  }

 private:
  // This is the entire state of LockedGuardPtr.
  SynchronizedType* const parent_{nullptr};
};

/**
 * 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.
 ************************************************************************/

// Non-member swap primitive
template <class T, class M>
void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
  lhs.swap(rhs);
}

/**
 * SYNCHRONIZED is the main facility that makes Synchronized<T>
 * helpful. It is a pseudo-statement that introduces a scope where the
 * object is locked. Inside that scope you get to access the unadorned
 * datum.
 *
 * Example:
 *
 * Synchronized<vector<int>> svector;
 * ...
 * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
 * or
 * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
 *
 * Refer to folly/docs/Synchronized.md for a detailed explanation and more
 * examples.
 */
#define SYNCHRONIZED(...)                                             \
  FOLLY_PUSH_WARNING                                                  \
  FOLLY_GCC_DISABLE_WARNING(shadow)                                   \
  if (bool SYNCHRONIZED_state = false) {                              \
  } else                                                              \
    for (auto SYNCHRONIZED_lockedPtr =                                \
             (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \
         !SYNCHRONIZED_state;                                         \
         SYNCHRONIZED_state = true)                                   \
      for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) =                \
               *SYNCHRONIZED_lockedPtr.operator->();                  \
           !SYNCHRONIZED_state;                                       \
           SYNCHRONIZED_state = true)                                 \
  FOLLY_POP_WARNING

#define TIMED_SYNCHRONIZED(timeout, ...)                                       \
  if (bool SYNCHRONIZED_state = false) {                                       \
  } else                                                                       \
    for (auto SYNCHRONIZED_lockedPtr =                                         \
             (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \
         !SYNCHRONIZED_state;                                                  \
         SYNCHRONIZED_state = true)                                            \
      for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) =                          \
               (!SYNCHRONIZED_lockedPtr                                        \
                    ? nullptr                                                  \
                    : SYNCHRONIZED_lockedPtr.operator->());                    \
           !SYNCHRONIZED_state;                                                \
           SYNCHRONIZED_state = true)

/**
 * Similar to SYNCHRONIZED, but only uses a read lock.
 */
#define SYNCHRONIZED_CONST(...)            \
  SYNCHRONIZED(                            \
      FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \
      (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())

/**
 * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
 */
#define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
  TIMED_SYNCHRONIZED(                          \
      timeout,                                 \
      FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)),     \
      (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).asConst())

/**
 * Temporarily disables synchronization inside a SYNCHRONIZED block.
 *
 * Note: This macro is deprecated, and kind of broken.  The input parameter
 * does not control what it unlocks--it always unlocks the lock acquired by the
 * most recent SYNCHRONIZED scope.  If you have two nested SYNCHRONIZED blocks,
 * UNSYNCHRONIZED always unlocks the inner-most, even if you pass in the
 * variable name used in the outer SYNCHRONIZED block.
 *
 * This macro will be removed soon in a subsequent diff.
 */
#define UNSYNCHRONIZED(name)                                             \
  for (auto SYNCHRONIZED_state3 = SYNCHRONIZED_lockedPtr.scopedUnlock(); \
       !SYNCHRONIZED_state;                                              \
       SYNCHRONIZED_state = true)                                        \
    for (auto& name = *SYNCHRONIZED_state3.getSynchronized();            \
         !SYNCHRONIZED_state;                                            \
         SYNCHRONIZED_state = true)

/**
 * Synchronizes two Synchronized objects (they may encapsulate
 * different data). Synchronization is done in increasing address of
 * object order, so there is no deadlock risk.
 */
#define SYNCHRONIZED_DUAL(n1, e1, n2, e2)                               \
  if (bool SYNCHRONIZED_state = false) {                                \
  } else                                                                \
    for (auto SYNCHRONIZED_ptrs = acquireLockedPair(e1, e2);            \
         !SYNCHRONIZED_state;                                           \
         SYNCHRONIZED_state = true)                                     \
      for (auto& n1 = *SYNCHRONIZED_ptrs.first; !SYNCHRONIZED_state;    \
           SYNCHRONIZED_state = true)                                   \
        for (auto& n2 = *SYNCHRONIZED_ptrs.second; !SYNCHRONIZED_state; \
             SYNCHRONIZED_state = true)

} /* namespace folly */
