/* * 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 #include #include // 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 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 */ template class LockInterfaceDispatcher { private: // assert that the mutex type has basic lock and unlock functions static_assert( folly::is_invocable::value, "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::value; static constexpr bool has_lock_shared = folly::is_invocable::value; static constexpr bool has_lock_upgrade = folly::is_invocable::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 LockTraitsImpl { 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 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. */ 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 struct LockTraitsImpl : public LockTraitsImpl { 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 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. * * Returns true or false indicating if the lock was acquired or not. */ template static bool try_lock_for( Mutex& mutex, const std::chrono::duration& 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 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. * * Returns true or false indicating if the lock was acquired or not. */ template static bool try_lock_for( Mutex& mutex, const std::chrono::duration& 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 static bool try_lock_shared_for( Mutex& mutex, const std::chrono::duration& timeout) { 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); } }; /** * 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