/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * 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 provides a traits class for describing properties about mutex
 * classes.
 *
 * This is a primitive for building higher-level abstractions that can work
 * with a variety of mutex classes.  For instance, this allows
 * folly::Synchronized to support a number of different mutex types.
 */
#pragma once

#include <chrono>
#include <type_traits>

#include <folly/functional/Invoke.h>

// Android, OSX, and Cygwin don't have timed mutexes
#if defined(ANDROID) || defined(__ANDROID__) || defined(__APPLE__) || \
    defined(__CYGWIN__)
#define FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES 0
#else
#define FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES 1
#endif

namespace folly {
namespace detail {

namespace member {
FOLLY_CREATE_MEMBER_INVOKER(lock_invoker, lock);
FOLLY_CREATE_MEMBER_INVOKER(try_lock_for_invoker, try_lock_for);
FOLLY_CREATE_MEMBER_INVOKER(lock_shared_invoker, lock_shared);
FOLLY_CREATE_MEMBER_INVOKER(lock_upgrade_invoker, lock_upgrade);
} // namespace member

/**
 * An enum to describe the "level" of a mutex.  The supported levels are
 *  Unique - a normal mutex that supports only exclusive locking
 *  Shared - a shared mutex which has shared locking and unlocking functions;
 *  Upgrade - a mutex that has all the methods of the two above along with
 *            support for upgradable locking
 */
enum class MutexLevel { UNIQUE, SHARED, UPGRADE };

/**
 * A template dispatch mechanism that is used to determine the level of the
 * mutex based on its interface.  As decided by LockInterfaceDispatcher.
 */
template <bool is_unique, bool is_shared, bool is_upgrade>
struct MutexLevelValueImpl;
template <>
struct MutexLevelValueImpl<true, false, false> {
  static constexpr MutexLevel value = MutexLevel::UNIQUE;
};
template <>
struct MutexLevelValueImpl<true, true, false> {
  static constexpr MutexLevel value = MutexLevel::SHARED;
};
template <>
struct MutexLevelValueImpl<true, true, true> {
  static constexpr MutexLevel value = MutexLevel::UPGRADE;
};

/**
 * An internal helper class to help identify the interface supported by the
 * mutex.  This is used in conjunction with the above MutexLevel
 * specializations and the LockTraitsImpl to determine what functions are
 * supported by objects of type Mutex
 */
template <class Mutex>
class LockInterfaceDispatcher {
 private:
  // assert that the mutex type has basic lock and unlock functions
  static_assert(
      folly::is_invocable_v<member::lock_invoker, Mutex>,
      "The mutex type must support lock and unlock functions");

  using duration = std::chrono::milliseconds;

 public:
  static constexpr bool has_lock_unique = true;
  static constexpr bool has_lock_timed =
      folly::is_invocable_v<member::try_lock_for_invoker, Mutex, duration>;
  static constexpr bool has_lock_shared =
      folly::is_invocable_v<member::lock_shared_invoker, Mutex>;
  static constexpr bool has_lock_upgrade =
      folly::is_invocable_v<member::lock_upgrade_invoker, Mutex>;
};

/**
 * LockTraitsImpl is the base that is used to desribe the interface used by
 * different mutex types.  It accepts a MutexLevel argument and a boolean to
 * show whether the mutex is a timed mutex or not.  The implementations are
 * partially specialized and inherit from the other implementations to get
 * similar functionality
 */
template <class Mutex, MutexLevel level, bool is_timed>
struct LockTraitsImpl;

template <class Mutex>
struct LockTraitsImpl<Mutex, MutexLevel::UNIQUE, false> {
  static constexpr bool is_timed{false};
  static constexpr bool is_shared{false};
  static constexpr bool is_upgrade{false};

  /**
   * Acquire the lock exclusively.
   */
  static void lock(Mutex& mutex) { mutex.lock(); }

  /**
   * Release an exclusively-held lock.
   */
  static void unlock(Mutex& mutex) { mutex.unlock(); }

  /**
   * Try to acquire the mutex
   */
  static bool try_lock(Mutex& mutex) { return mutex.try_lock(); }
};

/**
 * Higher level mutexes have all the capabilities of the lower levels so
 * inherit
 */
template <class Mutex>
struct LockTraitsImpl<Mutex, MutexLevel::SHARED, false>
    : public LockTraitsImpl<Mutex, MutexLevel::UNIQUE, false> {
  static constexpr bool is_timed{false};
  static constexpr bool is_shared{true};
  static constexpr bool is_upgrade{false};

  /**
   * Acquire the lock in shared (read) mode.
   */
  static void lock_shared(Mutex& mutex) { mutex.lock_shared(); }

  /**
   * Release a lock held in shared mode.
   */
  static void unlock_shared(Mutex& mutex) { mutex.unlock_shared(); }

  /**
   * Try to acquire the mutex in shared mode
   */
  static bool try_lock_shared(Mutex& mutex) { return mutex.try_lock_shared(); }
};

/**
 * The following methods are supported.  There are a few methods
 *
 *  m.lock_upgrade()
 *  m.unlock_upgrade()
 *  m.try_lock_upgrade()
 *
 *  m.unlock_upgrade_and_lock()
 *
 *  m.unlock_and_lock_upgrade()
 *  m.unlock_and_lock_shared()
 *  m.unlock_upgrade_and_lock_shared()
 *
 *  m.try_lock_upgrade_for(rel_time)
 *  m.try_unlock_upgrade_and_lock_for(rel_time)
 *
 * Upgrading a shared lock is likely to deadlock when there is more than one
 * thread performing an upgrade.  This applies both to upgrading a shared lock
 * to an upgrade lock and to upgrading a shared lock to a unique lock.
 *
 * Therefore, none of the following methods is supported:
 *  unlock_shared_and_lock_upgrade
 *  unlock_shared_and_lock
 *  try_unlock_shared_and_lock_upgrade
 *  try_unlock_shared_and_lock
 *  try_unlock_shared_and_lock_upgrade_for
 *  try_unlock_shared_and_lock_for
 */
template <class Mutex>
struct LockTraitsImpl<Mutex, MutexLevel::UPGRADE, false>
    : public LockTraitsImpl<Mutex, MutexLevel::SHARED, false> {
  static constexpr bool is_timed{false};
  static constexpr bool is_shared{true};
  static constexpr bool is_upgrade{true};

  /**
   * Acquire the lock in upgradable mode.
   */
  static void lock_upgrade(Mutex& mutex) { mutex.lock_upgrade(); }

  /**
   * Release the lock in upgrade mode
   */
  static void unlock_upgrade(Mutex& mutex) { mutex.unlock_upgrade(); }

  /**
   * Try and acquire the lock in upgrade mode
   */
  static bool try_lock_upgrade(Mutex& mutex) {
    return mutex.try_lock_upgrade();
  }

  /**
   * Upgrade from an upgradable state to an exclusive state
   */
  static void unlock_upgrade_and_lock(Mutex& mutex) {
    mutex.unlock_upgrade_and_lock();
  }

  /**
   * Downgrade from an exclusive state to an upgrade state
   */
  static void unlock_and_lock_upgrade(Mutex& mutex) {
    mutex.unlock_and_lock_upgrade();
  }

  /**
   * Downgrade from an exclusive state to a shared state
   */
  static void unlock_and_lock_shared(Mutex& mutex) {
    mutex.unlock_and_lock_shared();
  }

  /**
   * Downgrade from an upgrade state to a shared state
   */
  static void unlock_upgrade_and_lock_shared(Mutex& mutex) {
    mutex.unlock_upgrade_and_lock_shared();
  }
};

template <class Mutex>
struct LockTraitsImpl<Mutex, MutexLevel::UNIQUE, true>
    : public LockTraitsImpl<Mutex, MutexLevel::UNIQUE, false> {
  static constexpr bool is_timed{true};
  static constexpr bool is_shared{false};
  static constexpr bool is_upgrade{false};

  /**
   * Acquire the lock exclusively, with a timeout.
   *
   * Returns true or false indicating if the lock was acquired or not.
   */
  template <class Rep, class Period>
  static bool try_lock_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>& timeout) {
    return mutex.try_lock_for(timeout);
  }
};

/**
 * Note that there is no deadly diamond here because all the structs only have
 * static functions and static bools which are going to be overridden by the
 * lowest level implementation
 */
template <class Mutex>
struct LockTraitsImpl<Mutex, MutexLevel::SHARED, true>
    : public LockTraitsImpl<Mutex, MutexLevel::SHARED, false>,
      public LockTraitsImpl<Mutex, MutexLevel::UNIQUE, true> {
  static constexpr bool is_timed{true};
  static constexpr bool is_shared{true};
  static constexpr bool is_upgrade{false};

  /**
   * Acquire the lock exclusively, with a timeout.
   *
   * Returns true or false indicating if the lock was acquired or not.
   */
  template <class Rep, class Period>
  static bool try_lock_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>& timeout) {
    return mutex.try_lock_for(timeout);
  }

  /**
   * Acquire the lock in shared (read) mode, with a timeout.
   *
   * Returns true or false indicating if the lock was acquired or not.
   */
  template <class Rep, class Period>
  static bool try_lock_shared_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>& timeout) {
    return mutex.try_lock_shared_for(timeout);
  }
};

template <class Mutex>
struct LockTraitsImpl<Mutex, MutexLevel::UPGRADE, true>
    : public LockTraitsImpl<Mutex, MutexLevel::UPGRADE, false>,
      public LockTraitsImpl<Mutex, MutexLevel::SHARED, true> {
  static constexpr bool is_timed{true};
  static constexpr bool is_shared{true};
  static constexpr bool is_upgrade{true};

  /**
   * Acquire the lock in upgrade mode with a timeout
   *
   * Returns true or false indicating whether the lock was acquired or not
   */
  template <class Rep, class Period>
  static bool try_lock_upgrade_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>& timeout) {
    return mutex.try_lock_upgrade_for(timeout);
  }

  /**
   * Try to upgrade from an upgradable state to an exclusive state.
   *
   * Returns true or false indicating whether the lock was acquired or not
   */
  template <class Rep, class Period>
  static bool try_unlock_upgrade_and_lock_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>& timeout) {
    return mutex.try_unlock_upgrade_and_lock_for(timeout);
  }
};

/**
 * Unlock helpers
 *
 * These help in determining whether it is safe for Synchronized::LockedPtr
 * instances to be move assigned from one another.  It is safe if they both
 * have the same unlock policy, and it is not if they don't have the same
 * unlock policy.  For example
 *
 *    auto wlock = synchronized.wlock();
 *    wlock.unlock();
 *
 *    wlock = synchronized.rlock();
 *
 * This code would try to release the shared lock with a call to unlock(),
 * resulting in possibly undefined behavior.  By allowing the LockPolicy
 * classes (defined below) to know what their unlocking behavior is, we can
 * prevent against this by disabling unsafe conversions to and from
 * incompatible LockedPtr types (they are incompatible if the underlying
 * LockPolicy has different unlock policies.
 */
template <template <typename...> class LockTraits>
struct UnlockPolicyExclusive {
  constexpr static bool allows_concurrent_access = false;

  template <typename Mutex>
  static void unlock(Mutex& mutex) {
    LockTraits<Mutex>::unlock(mutex);
  }
};
template <template <typename...> class LockTraits>
struct UnlockPolicyShared {
  constexpr static bool allows_concurrent_access = true;

  template <typename Mutex>
  static void unlock(Mutex& mutex) {
    LockTraits<Mutex>::unlock_shared(mutex);
  }
};
template <template <typename...> class LockTraits>
struct UnlockPolicyUpgrade {
  constexpr static bool allows_concurrent_access = true;

  template <typename Mutex>
  static void unlock(Mutex& mutex) {
    LockTraits<Mutex>::unlock_upgrade(mutex);
  }
};

} // namespace detail

/**
 * LockTraits describes details about a particular mutex type.
 *
 * The default implementation automatically attempts to detect traits
 * based on the presence of various member functions.
 *
 * You can specialize LockTraits to provide custom behavior for lock
 * classes that do not use the standard method names
 * (lock()/unlock()/lock_shared()/unlock_shared()/try_lock_for())
 *
 *
 * LockTraits contains the following members variables:
 * - static constexpr bool is_shared
 *   True if the lock supports separate shared vs exclusive locking states.
 * - static constexpr bool is_timed
 *   True if the lock supports acquiring the lock with a timeout.
 * - static constexpr bool is_upgrade
 *   True if the lock supports an upgradable state
 *
 * The following static methods always exist:
 * - lock(Mutex& mutex)
 * - unlock(Mutex& mutex)
 * - try_lock(Mutex& mutex)
 *
 * The following static methods may exist, depending on is_shared, is_timed
 * and is_upgrade:
 * - lock_shared()
 * - try_lock_shared()
 *
 * - try_lock_for()
 * - try_lock_shared_for()
 *
 * - lock_upgrade()
 * - try_lock_upgrade()
 * - unlock_upgrade_and_lock()
 * - unlock_and_lock_upgrade()
 * - unlock_and_lock_shared()
 * - unlock_upgrade_and_lock_shared()
 *
 * - try_lock_upgrade_for()
 * - try_unlock_upgrade_and_lock_for()
 *
 * - unlock_shared()
 * - unlock_upgrade()
 */

/**
 * Decoupling LockTraits and LockTraitsBase so that if people want to fully
 * specialize LockTraits then they can inherit from LockTraitsBase instead
 * of LockTraits with all the same goodies :)
 */
template <class Mutex>
struct LockTraitsBase
    : public detail::LockTraitsImpl<
          Mutex,
          detail::MutexLevelValueImpl<
              detail::LockInterfaceDispatcher<Mutex>::has_lock_unique,
              detail::LockInterfaceDispatcher<Mutex>::has_lock_shared,
              detail::LockInterfaceDispatcher<Mutex>::has_lock_upgrade>::value,
          detail::LockInterfaceDispatcher<Mutex>::has_lock_timed> {};

template <class Mutex>
struct LockTraits : public LockTraitsBase<Mutex> {};

/*
 * Lock policy classes.
 *
 * These can be used as template parameters to provide compile-time
 * selection over the type of lock operation to perform.
 */
/**
 * A lock policy that performs exclusive lock operations.
 */
struct LockPolicyExclusive : detail::UnlockPolicyExclusive<LockTraits> {
  using UnlockPolicy = detail::UnlockPolicyExclusive<LockTraits>;

  template <class Mutex>
  static std::true_type lock(Mutex& mutex) {
    LockTraits<Mutex>::lock(mutex);
    return std::true_type{};
  }
  template <class Mutex, class Rep, class Period>
  static bool try_lock_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>& timeout) {
    return LockTraits<Mutex>::try_lock_for(mutex, timeout);
  }
};

/**
 * A lock policy that performs shared lock operations.
 * This policy only works with shared mutex types.
 */
struct LockPolicyShared : detail::UnlockPolicyShared<LockTraits> {
  using UnlockPolicy = detail::UnlockPolicyShared<LockTraits>;

  template <class Mutex>
  static std::true_type lock(Mutex& mutex) {
    LockTraits<Mutex>::lock_shared(mutex);
    return std::true_type{};
  }
  template <class Mutex, class Rep, class Period>
  static bool try_lock_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>& timeout) {
    return LockTraits<Mutex>::try_lock_shared_for(mutex, timeout);
  }
};

/**
 * A lock policy with the following mapping
 *
 *  lock() -> lock_upgrade()
 *  unlock() -> unlock_upgrade()
 *  try_lock_for -> try_lock_upgrade_for()
 */
struct LockPolicyUpgrade : detail::UnlockPolicyUpgrade<LockTraits> {
  using UnlockPolicy = detail::UnlockPolicyUpgrade<LockTraits>;

  template <class Mutex>
  static std::true_type lock(Mutex& mutex) {
    LockTraits<Mutex>::lock_upgrade(mutex);
    return std::true_type{};
  }
  template <class Mutex, class Rep, class Period>
  static bool try_lock_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>& timeout) {
    return LockTraits<Mutex>::try_lock_upgrade_for(mutex, timeout);
  }
};

/*****************************************************************************
 * Policies for optional mutex locking
 ****************************************************************************/
/**
 * A lock policy that tries to acquire write locks and returns true or false
 * based on whether the lock operation succeeds
 */
struct LockPolicyTryExclusive : detail::UnlockPolicyExclusive<LockTraits> {
  using UnlockPolicy = detail::UnlockPolicyExclusive<LockTraits>;

  template <class Mutex>
  static bool lock(Mutex& mutex) {
    return LockTraits<Mutex>::try_lock(mutex);
  }
};

/**
 * A lock policy that tries to acquire a read lock and returns true or false
 * based on whether the lock operation succeeds
 */
struct LockPolicyTryShared : detail::UnlockPolicyShared<LockTraits> {
  using UnlockPolicy = detail::UnlockPolicyShared<LockTraits>;

  template <class Mutex>
  static bool lock(Mutex& mutex) {
    return LockTraits<Mutex>::try_lock_shared(mutex);
  }
};

/**
 * A lock policy that tries to acquire an upgrade lock and returns true or
 * false based on whether the lock operation succeeds
 */
struct LockPolicyTryUpgrade : detail::UnlockPolicyUpgrade<LockTraits> {
  using UnlockPolicy = detail::UnlockPolicyUpgrade<LockTraits>;

  template <class Mutex>
  static bool lock(Mutex& mutex) {
    return LockTraits<Mutex>::try_lock_upgrade(mutex);
  }
};

/*****************************************************************************
 * Policies for all the transitions from possible mutex levels
 ****************************************************************************/
/**
 * A lock policy with the following mapping
 *
 *  lock() -> unlock_upgrade_and_lock()
 *  unlock() -> unlock()
 *  try_lock_for -> try_unlock_upgrade_and_lock_for()
 */
struct LockPolicyFromUpgradeToExclusive : LockPolicyExclusive {
  template <class Mutex>
  static std::true_type lock(Mutex& mutex) {
    LockTraits<Mutex>::unlock_upgrade_and_lock(mutex);
    return std::true_type{};
  }
  template <class Mutex, class Rep, class Period>
  static bool try_lock_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>& timeout) {
    return LockTraits<Mutex>::try_unlock_upgrade_and_lock_for(mutex, timeout);
  }
};

/**
 * A lock policy with the following mapping
 *
 *  lock() -> unlock_and_lock_upgrade()
 *  unlock() -> unlock_upgrade()
 *  try_lock_for -> unlock_and_lock_upgrade()
 */
struct LockPolicyFromExclusiveToUpgrade : LockPolicyUpgrade {
  template <class Mutex>
  static std::true_type lock(Mutex& mutex) {
    LockTraits<Mutex>::unlock_and_lock_upgrade(mutex);
    return std::true_type{};
  }
  template <class Mutex, class Rep, class Period>
  static bool try_lock_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>&) {
    LockTraits<Mutex>::unlock_and_lock_upgrade(mutex);

    // downgrade should be non blocking and should succeed
    return true;
  }
};

/**
 * A lock policy with the following mapping
 *
 *  lock() -> unlock_upgrade_and_lock_shared()
 *  unlock() -> unlock_shared()
 *  try_lock_for -> unlock_upgrade_and_lock_shared()
 */
struct LockPolicyFromUpgradeToShared : LockPolicyShared {
  template <class Mutex>
  static std::true_type lock(Mutex& mutex) {
    LockTraits<Mutex>::unlock_upgrade_and_lock_shared(mutex);
    return std::true_type{};
  }
  template <class Mutex, class Rep, class Period>
  static bool try_lock_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>&) {
    LockTraits<Mutex>::unlock_upgrade_and_lock_shared(mutex);

    // downgrade should be non blocking and should succeed
    return true;
  }
};

/**
 * A lock policy with the following mapping
 *
 *  lock() -> unlock_and_lock_shared()
 *  unlock() -> unlock_shared()
 *  try_lock_for() -> unlock_and_lock_shared()
 */
struct LockPolicyFromExclusiveToShared : LockPolicyShared {
  template <class Mutex>
  static std::true_type lock(Mutex& mutex) {
    LockTraits<Mutex>::unlock_and_lock_shared(mutex);
    return std::true_type{};
  }
  template <class Mutex, class Rep, class Period>
  static bool try_lock_for(
      Mutex& mutex, const std::chrono::duration<Rep, Period>&) {
    LockTraits<Mutex>::unlock_and_lock_shared(mutex);

    // downgrade should be non blocking and should succeed
    return true;
  }
};

} // namespace folly
