From 9276f2a5646a94eda765c92b171b98c499313213 Mon Sep 17 00:00:00 2001 From: Adam Simpkins Date: Tue, 5 Jul 2016 18:26:31 -0700 Subject: [PATCH 1/1] add LockTraits Summary: This adds a new LockTraits template class, for specifying how to use arbitrary custom mutex types. The goal of this new class is to replace the acquireRead(), acquireReadWrite(), releaseRead(), and releaseReadWrite() functions currently defined in Synchronized.h. I have not replaced these functions yet in this diff, but will do so in a follow-up diff. LockTraits has a few advantages over the existing methods: * It provides mechanisms for telling if a given mutex supports shared access and timed access. * The default LockTraits implementation automatically figures out the correct behavior for classes that define lock(), unlock(), methods. It automatically detects sharing and timed support based on the presence of lock_shared() and try_lock_for() methods. LockTraits can be specialized for custom lock types that do not conform with the lock method names used by the C++ standard. This does differ slightly from the argument dependent lookup mechanism used by the acquireRead() functions. Reviewed By: yfeldblum Differential Revision: D3504625 fbshipit-source-id: 40320997e9ae2147baecd10b70e8dc06a35e49e1 --- folly/LockTraits.h | 252 ++++++++++++++++++++++++++++++++++ folly/LockTraitsBoost.h | 93 +++++++++++++ folly/Makefile.am | 2 + folly/Synchronized.h | 20 +-- folly/test/LockTraitsTest.cpp | 203 +++++++++++++++++++++++++++ folly/test/Makefile.am | 4 + 6 files changed, 564 insertions(+), 10 deletions(-) create mode 100644 folly/LockTraits.h create mode 100644 folly/LockTraitsBoost.h create mode 100644 folly/test/LockTraitsTest.cpp diff --git a/folly/LockTraits.h b/folly/LockTraits.h new file mode 100644 index 00000000..3b0d21a2 --- /dev/null +++ b/folly/LockTraits.h @@ -0,0 +1,252 @@ +/* + * 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 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 + +// 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 { + +/** + * An internal helper class for identifying if a lock type supports + * lock_shared() and try_lock_for() methods. + */ +template +class LockTraitsImpl { + private: + // Helper functions for implementing the traits using SFINAE + template + static auto timed_lock_test(T*) -> typename std::is_same< + decltype(std::declval().try_lock_for(std::chrono::milliseconds(0))), + bool>::type; + template + static std::false_type timed_lock_test(...); + + template + static auto lock_shared_test(T*) -> typename std:: + is_same().lock_shared()), void>::type; + template + static std::false_type lock_shared_test(...); + + public: + static constexpr bool has_timed_lock = + decltype(timed_lock_test(0))::value; + static constexpr bool has_lock_shared = + decltype(lock_shared_test(0))::value; +}; + +template +struct LockTraitsUniqueBase { + /** + * Acquire the lock exclusively. + */ + static void lock(Mutex& mutex) { + mutex.lock(); + } + + /** + * Release an exclusively-held lock. + */ + static void unlock(Mutex& mutex) { + mutex.unlock(); + } +}; + +template +struct LockTraitsSharedBase : public LockTraitsUniqueBase { + /** + * 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(); + } +}; + +template +struct LockTraitsBase {}; + +template +struct LockTraitsBase + : public LockTraitsUniqueBase { + static constexpr bool is_shared = false; + static constexpr bool is_timed = false; +}; + +template +struct LockTraitsBase : public LockTraitsSharedBase { + static constexpr bool is_shared = true; + static constexpr bool is_timed = false; +}; + +template +struct LockTraitsBase : public LockTraitsUniqueBase { + static constexpr bool is_shared = false; + static constexpr bool is_timed = true; + + /** + * 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); + } +}; + +template +struct LockTraitsBase : public LockTraitsSharedBase { + static constexpr bool is_shared = true; + static constexpr bool is_timed = true; + + /** + * 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); + } +}; +} // 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. + * + * 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) + */ +template +struct LockTraits : public detail::LockTraitsBase< + Mutex, + detail::LockTraitsImpl::has_lock_shared, + detail::LockTraitsImpl::has_timed_lock> {}; + +/** + * If the lock is a shared lock, acquire it in shared mode. + * Otherwise, for plain (exclusive-only) locks, perform a normal acquire. + */ +template +typename std::enable_if::is_shared>::type +lock_shared_or_unique(Mutex& mutex) { + LockTraits::lock_shared(mutex); +} +template +typename std::enable_if::is_shared>::type +lock_shared_or_unique(Mutex& mutex) { + LockTraits::lock(mutex); +} + +/** + * If the lock is a shared lock, try to acquire it in shared mode, for up to + * the given timeout. Otherwise, for plain (exclusive-only) locks, try to + * perform a normal acquire. + * + * Returns true if the lock was acquired, or false on time out. + */ +template +typename std::enable_if::is_shared, bool>::type +try_lock_shared_or_unique_for( + Mutex& mutex, + const std::chrono::duration& timeout) { + return LockTraits::try_lock_shared_for(mutex, timeout); +} +template +typename std::enable_if::is_shared, bool>::type +try_lock_shared_or_unique_for( + Mutex& mutex, + const std::chrono::duration& timeout) { + return LockTraits::try_lock_for(mutex, timeout); +} + +/** + * Release a lock acquired with lock_shared_or_unique() + */ +template +typename std::enable_if::is_shared>::type +unlock_shared_or_unique(Mutex& mutex) { + LockTraits::unlock_shared(mutex); +} +template +typename std::enable_if::is_shared>::type +unlock_shared_or_unique(Mutex& mutex) { + LockTraits::unlock(mutex); +} +} // folly diff --git a/folly/LockTraitsBoost.h b/folly/LockTraitsBoost.h new file mode 100644 index 00000000..dd9a17b6 --- /dev/null +++ b/folly/LockTraitsBoost.h @@ -0,0 +1,93 @@ +/* + * 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 file contains LockTraits specializations for boost mutex types. + * + * These need to be specialized simply due to the fact that the timed + * methods take boost::chrono arguments instead of std::chrono. + */ +#pragma once + +#include + +#if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES + +#include + +namespace folly { + +/** + * LockTraits specialization for boost::shared_mutex + */ +template <> +struct LockTraits + : public folly::detail::LockTraitsSharedBase { + static constexpr bool is_shared = true; + static constexpr bool is_timed = true; + + static bool try_lock_for( + boost::shared_mutex& mutex, + std::chrono::milliseconds timeout) { + // Convert the std::chrono argument to boost::chrono + return mutex.try_lock_for(boost::chrono::milliseconds(timeout.count())); + } + + static bool try_lock_shared_for( + boost::shared_mutex& mutex, + std::chrono::milliseconds timeout) { + // Convert the std::chrono argument to boost::chrono + return mutex.try_lock_shared_for( + boost::chrono::milliseconds(timeout.count())); + } +}; + +/** + * LockTraits specialization for boost::timed_mutex + */ +template <> +struct LockTraits + : public folly::detail::LockTraitsUniqueBase { + static constexpr bool is_shared = false; + static constexpr bool is_timed = true; + + static bool try_lock_for( + boost::timed_mutex& mutex, + std::chrono::milliseconds timeout) { + // Convert the std::chrono argument to boost::chrono + return mutex.try_lock_for(boost::chrono::milliseconds(timeout.count())); + } +}; + +/** + * LockTraits specialization for boost::recursive_timed_mutex + */ +template <> +struct LockTraits + : public folly::detail::LockTraitsUniqueBase { + static constexpr bool is_shared = false; + static constexpr bool is_timed = true; + + static bool try_lock_for( + boost::recursive_timed_mutex& mutex, + std::chrono::milliseconds timeout) { + // Convert the std::chrono argument to boost::chrono + return mutex.try_lock_for(boost::chrono::milliseconds(timeout.count())); + } +}; +} // folly + +#endif // FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES diff --git a/folly/Makefile.am b/folly/Makefile.am index 998ea6d4..9df86b5b 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -229,6 +229,8 @@ nobase_follyinclude_HEADERS = \ Lazy.h \ LifoSem.h \ Likely.h \ + LockTraits.h \ + LockTraitsBoost.h \ Logging.h \ MacAddress.h \ Malloc.h \ diff --git a/folly/Synchronized.h b/folly/Synchronized.h index 4b6a25b8..f9fee2ab 100644 --- a/folly/Synchronized.h +++ b/folly/Synchronized.h @@ -23,12 +23,20 @@ #pragma once -#include -#include #include +#include #include #include #include +#include +#include + +// Temporarily provide FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES under the legacy +// FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES name. This definition will be +// removed shortly in an upcoming diff to make Synchronized fully utilize +// LockTraits. +#define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES \ + FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES namespace folly { @@ -39,14 +47,6 @@ enum InternalDoNotUse {}; * Free function adaptors for std:: and boost:: */ -// Android, OSX, and Cygwin don't have timed mutexes -#if defined(ANDROID) || defined(__ANDROID__) || \ - defined(__APPLE__) || defined(__CYGWIN__) -# define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES 0 -#else -# define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES 1 -#endif - /** * Yields true iff T has .lock() and .unlock() member functions. This * is done by simply enumerating the mutexes with this interface in diff --git a/folly/test/LockTraitsTest.cpp b/folly/test/LockTraitsTest.cpp new file mode 100644 index 00000000..df43a8a0 --- /dev/null +++ b/folly/test/LockTraitsTest.cpp @@ -0,0 +1,203 @@ +/* + * 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. + */ +#include +#include + +#include +#include + +#include +#include +#include + +using namespace folly; + +TEST(LockTraits, std_mutex) { + using traits = LockTraits; + static_assert(!traits::is_timed, "std:mutex is not a timed lock"); + static_assert(!traits::is_shared, "std:mutex is not a shared lock"); + + std::mutex mutex; + traits::lock(mutex); + traits::unlock(mutex); + + lock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); +} + +TEST(LockTraits, SharedMutex) { + using traits = LockTraits; + static_assert(traits::is_timed, "SharedMutex is a timed lock"); + static_assert(traits::is_shared, "SharedMutex is a shared lock"); + + SharedMutex mutex; + traits::lock(mutex); + traits::unlock(mutex); + + traits::lock_shared(mutex); + traits::lock_shared(mutex); + traits::unlock_shared(mutex); + traits::unlock_shared(mutex); + + lock_shared_or_unique(mutex); + lock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); +} + +TEST(LockTraits, SpinLock) { + using traits = LockTraits; + static_assert(!traits::is_timed, "folly::SpinLock is not a timed lock"); + static_assert(!traits::is_shared, "folly::SpinLock is not a shared lock"); + + SpinLock mutex; + traits::lock(mutex); + traits::unlock(mutex); + + lock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); +} + +TEST(LockTraits, RWSpinLock) { + using traits = LockTraits; + static_assert(!traits::is_timed, "folly::RWSpinLock is not a timed lock"); + static_assert(traits::is_shared, "folly::RWSpinLock is a shared lock"); + + RWSpinLock mutex; + traits::lock(mutex); + traits::unlock(mutex); + + traits::lock_shared(mutex); + traits::lock_shared(mutex); + traits::unlock_shared(mutex); + traits::unlock_shared(mutex); + + lock_shared_or_unique(mutex); + lock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); +} + +TEST(LockTraits, boost_mutex) { + using traits = LockTraits; + static_assert(!traits::is_timed, "boost::mutex is not a timed lock"); + static_assert(!traits::is_shared, "boost::mutex is not a shared lock"); + + boost::mutex mutex; + traits::lock(mutex); + traits::unlock(mutex); + + lock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); +} + +TEST(LockTraits, boost_recursive_mutex) { + using traits = LockTraits; + static_assert( + !traits::is_timed, "boost::recursive_mutex is not a timed lock"); + static_assert( + !traits::is_shared, "boost::recursive_mutex is not a shared lock"); + + boost::recursive_mutex mutex; + traits::lock(mutex); + traits::lock(mutex); + traits::unlock(mutex); + traits::unlock(mutex); + + lock_shared_or_unique(mutex); + lock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); +} + +#if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES +TEST(LockTraits, timed_mutex) { + using traits = LockTraits; + static_assert(traits::is_timed, "std::timed_mutex is a timed lock"); + static_assert(!traits::is_shared, "std::timed_mutex is not a shared lock"); + + std::timed_mutex mutex; + traits::lock(mutex); + bool gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1)); + EXPECT_FALSE(gotLock) << "should not have been able to acquire the " + << "timed_mutex a second time"; + traits::unlock(mutex); + + lock_shared_or_unique(mutex); + gotLock = try_lock_shared_or_unique_for(mutex, std::chrono::milliseconds(1)); + EXPECT_FALSE(gotLock) << "should not have been able to acquire the " + << "timed_mutex a second time"; + unlock_shared_or_unique(mutex); +} + +TEST(LockTraits, recursive_timed_mutex) { + using traits = LockTraits; + static_assert(traits::is_timed, "std::recursive_timed_mutex is a timed lock"); + static_assert( + !traits::is_shared, "std::recursive_timed_mutex is not a shared lock"); + + std::recursive_timed_mutex mutex; + traits::lock(mutex); + auto gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(10)); + EXPECT_TRUE(gotLock) << "should have been able to acquire the " + << "recursive_timed_mutex a second time"; + traits::unlock(mutex); + traits::unlock(mutex); + + lock_shared_or_unique(mutex); + gotLock = try_lock_shared_or_unique_for(mutex, std::chrono::milliseconds(10)); + EXPECT_TRUE(gotLock) << "should have been able to acquire the " + << "recursive_timed_mutex a second time"; + unlock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); +} + +TEST(LockTraits, boost_shared_mutex) { + using traits = LockTraits; + static_assert(traits::is_timed, "boost::shared_mutex is a timed lock"); + static_assert(traits::is_shared, "boost::shared_mutex is a shared lock"); + + boost::shared_mutex mutex; + traits::lock(mutex); + auto gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1)); + EXPECT_FALSE(gotLock) << "should not have been able to acquire the " + << "shared_mutex a second time"; + gotLock = traits::try_lock_shared_for(mutex, std::chrono::milliseconds(1)); + EXPECT_FALSE(gotLock) << "should not have been able to acquire the " + << "shared_mutex a second time"; + traits::unlock(mutex); + + traits::lock_shared(mutex); + gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1)); + EXPECT_FALSE(gotLock) << "should not have been able to acquire the " + << "shared_mutex a second time"; + gotLock = traits::try_lock_shared_for(mutex, std::chrono::milliseconds(10)); + EXPECT_TRUE(gotLock) << "should have been able to acquire the " + << "shared_mutex a second time in shared mode"; + traits::unlock_shared(mutex); + traits::unlock_shared(mutex); + + lock_shared_or_unique(mutex); + gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1)); + EXPECT_FALSE(gotLock) << "should not have been able to acquire the " + << "shared_mutex a second time"; + gotLock = try_lock_shared_or_unique_for(mutex, std::chrono::milliseconds(10)); + EXPECT_TRUE(gotLock) << "should have been able to acquire the " + << "shared_mutex a second time in shared mode"; + unlock_shared_or_unique(mutex); + unlock_shared_or_unique(mutex); +} +#endif // FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES diff --git a/folly/test/Makefile.am b/folly/test/Makefile.am index 1857b32b..eb338cae 100644 --- a/folly/test/Makefile.am +++ b/folly/test/Makefile.am @@ -149,6 +149,10 @@ synchronized_test_SOURCES = SynchronizedTest.cpp synchronized_test_LDADD = libfollytestmain.la TESTS += synchronized_test +lock_traits_test_SOURCES = LockTraitsTest.cpp +lock_traits_test_LDADD = libfollytestmain.la +TESTS += lock_traits_test + concurrent_skiplist_test_SOURCES = ConcurrentSkipListTest.cpp concurrent_skiplist_test_LDADD = libfollytestmain.la TESTS += concurrent_skiplist_test -- 2.34.1