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