X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=blobdiff_plain;f=folly%2FLockTraits.h;h=1879e3bc9a769e0fdacaf2b4937ea1bef0387a87;hp=3b84f503e54524ed55335ea52a84a88e68249470;hb=84f24236e480f1ab287f07c8d862df8928b45115;hpb=b22e53e035bce899409e19cf4f32d8b251758422 diff --git a/folly/LockTraits.h b/folly/LockTraits.h index 3b84f503..1879e3bc 100644 --- a/folly/LockTraits.h +++ b/folly/LockTraits.h @@ -39,12 +39,50 @@ namespace folly { namespace detail { /** - * An internal helper class for identifying if a lock type supports - * lock_shared() and try_lock_for() methods. + * 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 +struct MutexLevelValueImpl; +template <> +struct MutexLevelValueImpl { + static constexpr MutexLevel value = MutexLevel::UNIQUE; +}; +template <> +struct MutexLevelValueImpl { + static constexpr MutexLevel value = MutexLevel::SHARED; +}; +template <> +struct MutexLevelValueImpl { + 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 + * + * The implementation uses SINAE in the return value with trailing return + * types to figure out what level a mutex is */ template -class LockTraitsImpl { +class LockInterfaceDispatcher { private: + // assert that the mutex type has basic lock and unlock functions + static_assert( + std::is_same().lock()), void>::value, + "The mutex type must support lock and unlock functions"); + // Helper functions for implementing the traits using SFINAE template static auto timed_lock_test(T*) -> typename std::is_same< @@ -59,15 +97,38 @@ class LockTraitsImpl { template static std::false_type lock_shared_test(...); + template + static auto lock_upgrade_test(T*) -> typename std:: + is_same().lock_upgrade()), void>::type; + template + static std::false_type lock_upgrade_test(...); + public: - static constexpr bool has_timed_lock = + static constexpr bool has_lock_unique = true; + static constexpr bool has_lock_timed = decltype(timed_lock_test(0))::value; static constexpr bool has_lock_shared = decltype(lock_shared_test(0))::value; + static constexpr bool has_lock_upgrade = + decltype(lock_upgrade_test(0))::value; }; +/** + * 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 +struct LockTraitsImpl; + template -struct LockTraitsUniqueBase { +struct LockTraitsImpl { + static constexpr bool is_timed{false}; + static constexpr bool is_shared{false}; + static constexpr bool is_upgrade{false}; + /** * Acquire the lock exclusively. */ @@ -83,8 +144,17 @@ struct LockTraitsUniqueBase { } }; +/** + * Higher level mutexes have all the capabilities of the lower levels so + * inherit + */ template -struct LockTraitsSharedBase : public LockTraitsUniqueBase { +struct LockTraitsImpl + : public LockTraitsImpl { + 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. */ @@ -100,26 +170,89 @@ struct LockTraitsSharedBase : public LockTraitsUniqueBase { } }; -template -struct LockTraitsBase {}; - +/** + * The following methods are supported. There are a few methods + * + * m.lock_upgrade() + * m.unlock_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 -struct LockTraitsBase - : public LockTraitsUniqueBase { - static constexpr bool is_shared = false; - static constexpr bool is_timed = false; -}; +struct LockTraitsImpl + : public LockTraitsImpl { + static constexpr bool is_timed{false}; + static constexpr bool is_shared{true}; + static constexpr bool is_upgrade{true}; -template -struct LockTraitsBase : public LockTraitsSharedBase { - static constexpr bool is_shared = true; - static constexpr bool is_timed = false; + /** + * 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(); + } + + /** + * 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 -struct LockTraitsBase : public LockTraitsUniqueBase { - static constexpr bool is_shared = false; - static constexpr bool is_timed = true; +struct LockTraitsImpl + : public LockTraitsImpl { + 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. @@ -134,10 +267,18 @@ struct LockTraitsBase : public LockTraitsUniqueBase { } }; +/** + * 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 -struct LockTraitsBase : public LockTraitsSharedBase { - static constexpr bool is_shared = true; - static constexpr bool is_timed = true; +struct LockTraitsImpl + : public LockTraitsImpl, + public LockTraitsImpl { + 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. @@ -163,6 +304,40 @@ struct LockTraitsBase : public LockTraitsSharedBase { return mutex.try_lock_shared_for(timeout); } }; + +template +struct LockTraitsImpl + : public LockTraitsImpl, + public LockTraitsImpl { + 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 + static bool try_lock_upgrade_for( + Mutex& mutex, + const std::chrono::duration& 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 + static bool try_unlock_upgrade_and_lock_for( + Mutex& mutex, + const std::chrono::duration& timeout) { + return mutex.try_unlock_upgrade_and_lock_for(timeout); + } +}; + } // detail /** @@ -181,23 +356,50 @@ struct LockTraitsBase : public LockTraitsSharedBase { * 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) * - * The following static methods may exist, depending on is_shared and - * is_timed: - * - try_lock_for(Mutex& mutex, timeout) - * - lock_shared(Mutex& mutex) - * - unlock_shared(Mutex& mutex) - * - try_lock_shared_for(Mutex& mutex, timeout) + * The following static methods may exist, depending on is_shared, is_timed + * and is_upgrade: + * - lock_shared() + * + * - try_lock_for() + * - try_lock_shared_for() + * + * - 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 +struct LockTraitsBase + : public detail::LockTraitsImpl< + Mutex, + detail::MutexLevelValueImpl< + detail::LockInterfaceDispatcher::has_lock_unique, + detail::LockInterfaceDispatcher::has_lock_shared, + detail::LockInterfaceDispatcher::has_lock_upgrade>::value, + detail::LockInterfaceDispatcher::has_lock_timed> {}; + template -struct LockTraits : public detail::LockTraitsBase< - Mutex, - detail::LockTraitsImpl::has_lock_shared, - detail::LockTraitsImpl::has_timed_lock> {}; +struct LockTraits : public LockTraitsBase {}; /** * If the lock is a shared lock, acquire it in shared mode.