Last active
November 11, 2023 17:23
-
-
Save Seneral/4cee137118ff262d95424aa3c5599439 to your computer and use it in GitHub Desktop.
Standalone Folly Synchronized - With std::shared_mutex (no upgrade) - see revision for changes from original
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) Meta Platforms, Inc. and 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. | |
*/ | |
#pragma once | |
#include <chrono> | |
#include <mutex> | |
#include <shared_mutex> | |
#include <system_error> | |
#include <type_traits> | |
/** | |
* NOTE: Dropped unsafe_for_async_usage_if (tagging structs with folly_is_unsafe_for_async_usage) | |
* That custom tagging system was used for their SharedMutex implementation | |
* Since it is not used in this standalone version, it is dropped | |
*/ | |
template<typename ...Ts> struct make_void | |
{ | |
using type = void; | |
}; | |
template<typename ...Ts> using void_t = typename make_void<Ts...>::type; | |
// https://en.cppreference.com/w/cpp/experimental/is_detected | |
namespace folly | |
{ | |
namespace detail | |
{ | |
struct nonesuch | |
{ | |
nonesuch() = delete; | |
~nonesuch() = delete; | |
nonesuch(nonesuch const&) = delete; | |
nonesuch(nonesuch const&&) = delete; | |
void operator=(nonesuch const&) = delete; | |
void operator=(nonesuch&&) = delete; | |
}; | |
template<class Default, | |
class AlwaysVoid, | |
template<class...> class Op, | |
class... Args> | |
struct detector | |
{ | |
using value_t = std::false_type; | |
using type = Default; | |
}; | |
template<class Default, template<class...> class Op, class... Args> | |
struct detector<Default, void_t<Op<Args...>>, Op, Args...> | |
{ | |
using value_t = std::true_type; | |
using type = Op<Args...>; | |
}; | |
template<template<class...> class Op, class... Args> | |
using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t; | |
template<template<class...> class Op, class... Args> | |
using detected_t = typename detector<nonesuch, void, Op, Args...>::type; | |
template<class Default, template<class...> class Op, class... Args> | |
using detected_or = detector<Default, void, Op, Args...>; | |
template<class Default, template<class...> class Op, class... Args> | |
using detected_or_t = typename detected_or<Default, Op, Args...>::type; | |
template<class Expected, template<class...> class Op, class... Args> | |
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>; | |
template<class To, template<class...> class Op, class... Args> | |
using is_detected_convertible = | |
std::is_convertible<detected_t<Op, Args...>, To>; | |
} // namespace detail | |
} // namespace folly | |
namespace folly { | |
namespace access { | |
#define FOLLY_CREATE_MEMBER_INVOKER_SUITE(membername) \ | |
struct membername##_fn { \ | |
template <typename O, typename... Args> \ | |
inline constexpr auto operator()( \ | |
O&& o, Args&&... args) const \ | |
noexcept(noexcept( \ | |
static_cast<O&&>(o).membername(static_cast<Args&&>(args)...))) \ | |
-> decltype(static_cast<O&&>(o).membername( \ | |
static_cast<Args&&>(args)...)) { \ | |
return static_cast<O&&>(o).membername(static_cast<Args&&>(args)...); \ | |
} \ | |
}; \ | |
inline constexpr membername##_fn membername {} | |
// locks and unlocks | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(lock); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_for); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_until); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(lock_shared); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_shared); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_shared_for); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_shared_until); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_shared); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(lock_upgrade); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_upgrade); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_upgrade_for); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_lock_upgrade_until); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_upgrade); | |
// transitions | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_and_lock_shared); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_and_lock_upgrade); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock_for); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock_until); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock_upgrade); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock_upgrade_for); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_shared_and_lock_upgrade_until); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_upgrade_and_lock); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_upgrade_and_lock); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_upgrade_and_lock_for); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(try_unlock_upgrade_and_lock_until); | |
FOLLY_CREATE_MEMBER_INVOKER_SUITE(unlock_upgrade_and_lock_shared); | |
} // namespace access | |
namespace detail { | |
// Ordinarily, there would not be any need for this lock_storage class and the | |
// lock_base class can just have the entire implementation with a little bit | |
// of sfinae to handle with-state v.s. sans-state. | |
// | |
// Unfortunately, vc2017 fails to resolve calls to the adopt_lock ctor of such | |
// a lock_base class implemented with sfinae. The only observed workaround is | |
// to extract the adopt_lock ctor to a pair of dependent base classes, plus the | |
// minimum necessary to make it all work. In particular, none of the locking or | |
// unlocking functions are needed here, which makes lock_storage actually quite | |
// minimal. | |
// | |
// Unfortunately, this workaround leaves lock_base marvelously odd with extra | |
// syntax noise everywhere: c'est la vie. | |
template <typename Mutex, typename LockState> | |
struct lock_storage { | |
Mutex* mutex_{}; | |
LockState state_{}; | |
lock_storage() = default; | |
lock_storage(lock_storage&& that) noexcept | |
: mutex_{std::exchange(that.mutex_, nullptr)}, | |
state_{std::exchange(that.state_, LockState{})} {} | |
lock_storage(Mutex& mutex, std::adopt_lock_t, LockState const& state) | |
: mutex_{std::addressof(mutex)}, state_{state} { | |
state_ || (check_fail_(), 0); | |
} | |
void operator=(lock_storage&&) = delete; | |
private: | |
[[noreturn]] void check_fail_() { | |
auto code = std::errc::operation_not_permitted; | |
throw std::system_error(std::make_error_code(code)); | |
} | |
}; | |
template <typename Mutex> | |
struct lock_storage<Mutex, void> { | |
Mutex* mutex_{}; | |
bool state_{}; | |
lock_storage() = default; | |
lock_storage(lock_storage&& that) noexcept | |
: mutex_{std::exchange(that.mutex_, nullptr)}, | |
state_{std::exchange(that.state_, false)} {} | |
lock_storage(Mutex& mutex, std::adopt_lock_t) | |
: mutex_{std::addressof(mutex)}, state_{true} {} | |
void operator=(lock_storage&&) = delete; | |
}; | |
// A lock base class with a mostly-complete implementation suitable for either | |
// unique, shared, or upgrade lock base classes. However, each particular base | |
// class specific to each lock category must still be its own class to avoid | |
// overly permissive overloads of member and free swap. | |
template <typename Mutex, typename Policy> | |
class lock_base // | |
: private lock_storage< | |
Mutex, | |
std::invoke_result_t<typename Policy::lock_fn, Mutex&>> { | |
public: | |
using mutex_type = Mutex; | |
using state_type = std::invoke_result_t<typename Policy::lock_fn, mutex_type&>; | |
static_assert( | |
std::is_same<state_type, std::decay_t<state_type>>::value, | |
"state_type, if not void, must be a value type"); | |
static_assert( | |
std::is_void<state_type>::value || | |
(std::is_nothrow_default_constructible<state_type>::value && | |
std::is_nothrow_copy_constructible<state_type>::value && | |
std::is_nothrow_copy_assignable<state_type>::value && | |
std::is_nothrow_destructible<state_type>::value), | |
"state_type, if not void, must be noexcept-semiregular"); | |
static_assert( | |
std::is_void<state_type>::value || | |
std::is_constructible<bool, state_type>::value, | |
"state_type, if not void, must explicitly convert to bool"); | |
private: | |
using storage = lock_storage<mutex_type, state_type>; | |
static constexpr bool has_state_ = !std::is_void<state_type>::value; | |
template <bool C> | |
using if_ = std::enable_if_t<C, int>; | |
public: | |
using storage::storage; | |
lock_base() = default; | |
lock_base(lock_base&&) = default; | |
explicit lock_base(mutex_type& mutex) { | |
storage::mutex_ = std::addressof(mutex); | |
lock(); | |
} | |
lock_base(mutex_type& mutex, std::defer_lock_t) noexcept { | |
storage::mutex_ = std::addressof(mutex); | |
} | |
lock_base(mutex_type& mutex, std::try_to_lock_t) { | |
storage::mutex_ = std::addressof(mutex); | |
try_lock(); | |
} | |
template <typename Rep, typename Period> | |
lock_base( | |
mutex_type& mutex, std::chrono::duration<Rep, Period> const& timeout) { | |
storage::mutex_ = std::addressof(mutex); | |
try_lock_for(timeout); | |
} | |
template <typename Clock, typename Duration> | |
lock_base( | |
mutex_type& mutex, | |
std::chrono::time_point<Clock, Duration> const& deadline) { | |
storage::mutex_ = std::addressof(mutex); | |
try_lock_until(deadline); | |
} | |
~lock_base() { | |
if (owns_lock()) { | |
unlock(); | |
} | |
} | |
lock_base& operator=(lock_base&& that) noexcept { | |
if (owns_lock()) { | |
unlock(); | |
} | |
storage::mutex_ = std::exchange(that.mutex_, nullptr); | |
storage::state_ = std::exchange(that.state_, decltype(storage::state_){}); | |
return *this; | |
} | |
template <bool C = has_state_, if_<!C> = 0> | |
void lock() { | |
check<false>(); | |
typename Policy::lock_fn{}(*storage::mutex_); | |
storage::state_ = true; | |
} | |
template <bool C = has_state_, if_<C> = 0> | |
void lock() { | |
check<false>(); | |
storage::state_ = typename Policy::lock_fn{}(*storage::mutex_); | |
} | |
bool try_lock() { | |
check<false>(); | |
storage::state_ = typename Policy::try_lock_fn{}(*storage::mutex_); | |
return !!storage::state_; | |
} | |
template <typename Rep, typename Period> | |
bool try_lock_for(std::chrono::duration<Rep, Period> const& timeout) { | |
check<false>(); | |
storage::state_ = | |
typename Policy::try_lock_for_fn{}(*storage::mutex_, timeout); | |
return !!storage::state_; | |
} | |
template <typename Clock, typename Duration> | |
bool try_lock_until( | |
std::chrono::time_point<Clock, Duration> const& deadline) { | |
check<false>(); | |
storage::state_ = | |
typename Policy::try_lock_until_fn{}(*storage::mutex_, deadline); | |
return !!storage::state_; | |
} | |
template <bool C = has_state_, if_<!C> = 0> | |
void unlock() { | |
check<true>(); | |
typename Policy::unlock_fn{}(*storage::mutex_); | |
storage::state_ = decltype(storage::state_){}; | |
} | |
template <bool C = has_state_, if_<C> = 0> | |
void unlock() { | |
check<true>(); | |
auto const& state = storage::state_; // prevent unlock from mutating state_ | |
typename Policy::unlock_fn{}(*storage::mutex_, state); | |
storage::state_ = decltype(storage::state_){}; | |
} | |
mutex_type* release() noexcept { | |
storage::state_ = {}; | |
return std::exchange(storage::mutex_, nullptr); | |
} | |
mutex_type* mutex() const noexcept { return storage::mutex_; } | |
template <bool C = has_state_, if_<C> = 0> | |
state_type state() const noexcept { | |
return storage::state_; | |
} | |
bool owns_lock() const noexcept { return !!storage::state_; } | |
explicit operator bool() const noexcept { return !!storage::state_; } | |
protected: | |
void swap(lock_base& that) noexcept { | |
std::swap(storage::mutex_, that.mutex_); | |
std::swap(storage::state_, that.state_); | |
} | |
private: | |
template <bool Owns> | |
void check() { | |
if (!storage::mutex_ || !storage::state_ == Owns) { | |
check_fail_<Owns>(); | |
} | |
} | |
template <bool Owns> | |
[[noreturn]] void check_fail_() { | |
auto perm = std::errc::operation_not_permitted; | |
auto dead = std::errc::resource_deadlock_would_occur; | |
auto code = !storage::mutex_ || !storage::state_ ? perm : dead; | |
throw std::system_error(std::make_error_code(code)); | |
} | |
}; | |
template <typename Mutex, typename Policy> | |
class lock_guard_base { | |
private: | |
using lock_type_ = lock_base<Mutex, Policy>; | |
using lock_state_type_ = typename lock_type_::state_type; | |
static constexpr bool has_state_ = !std::is_void<lock_state_type_>::value; | |
using state_type_ = std::conditional_t<has_state_, lock_state_type_, bool>; | |
template <bool C> | |
using if_ = std::enable_if_t<C, int>; | |
public: | |
using mutex_type = Mutex; | |
lock_guard_base(lock_guard_base const&) = delete; | |
lock_guard_base(lock_guard_base&&) = delete; | |
explicit lock_guard_base(mutex_type& mutex) : lock_{mutex} {} | |
template <bool C = has_state_, if_<!C> = 0> | |
lock_guard_base(mutex_type& mutex, std::adopt_lock_t) | |
: lock_{mutex, std::adopt_lock} {} | |
template <bool C = has_state_, if_<C> = 0> | |
lock_guard_base( | |
mutex_type& mutex, std::adopt_lock_t, state_type_ const& state) | |
: lock_{mutex, std::adopt_lock, state} {} | |
void operator=(lock_guard_base const&) = delete; | |
void operator=(lock_guard_base&&) = delete; | |
private: | |
lock_type_ lock_; | |
}; | |
struct lock_policy_unique { | |
using lock_fn = access::lock_fn; | |
using try_lock_fn = access::try_lock_fn; | |
using try_lock_for_fn = access::try_lock_for_fn; | |
using try_lock_until_fn = access::try_lock_until_fn; | |
using unlock_fn = access::unlock_fn; | |
}; | |
struct lock_policy_shared { | |
using lock_fn = access::lock_shared_fn; | |
using try_lock_fn = access::try_lock_shared_fn; | |
using try_lock_for_fn = access::try_lock_shared_for_fn; | |
using try_lock_until_fn = access::try_lock_shared_until_fn; | |
using unlock_fn = access::unlock_shared_fn; | |
}; | |
struct lock_policy_upgrade { | |
using lock_fn = access::lock_upgrade_fn; | |
using try_lock_fn = access::try_lock_upgrade_fn; | |
using try_lock_for_fn = access::try_lock_upgrade_for_fn; | |
using try_lock_until_fn = access::try_lock_upgrade_until_fn; | |
using unlock_fn = access::unlock_upgrade_fn; | |
}; | |
template <typename Mutex> | |
using lock_policy_hybrid = std::conditional_t< | |
std::is_invocable_v<access::lock_shared_fn, Mutex&>, | |
lock_policy_shared, | |
lock_policy_unique>; | |
template <typename Mutex> | |
using lock_base_unique = lock_base<Mutex, lock_policy_unique>; | |
template <typename Mutex> | |
using lock_base_shared = lock_base<Mutex, lock_policy_shared>; | |
template <typename Mutex> | |
using lock_base_upgrade = lock_base<Mutex, lock_policy_upgrade>; | |
template <typename Mutex> | |
using lock_base_hybrid = lock_base<Mutex, lock_policy_hybrid<Mutex>>; | |
} // namespace detail | |
// unique_lock_base | |
// | |
// A lock-holder base which holds exclusive locks, usable with any mutex type. | |
// | |
// Works with both lockable mutex types and lockable-with-state mutex types. | |
// | |
// When defining lockable-with-state mutex types, specialize std::unique_lock | |
// to derive this. See the example with upgrade_lock. | |
// | |
// A lockable-with-state mutex type is signalled by the return type of mutex | |
// member function lock. Members try_lock, try_lock_for, and try_lock_until | |
// all return this type and member unlock accepts this type. | |
template <typename Mutex> | |
class unique_lock_base : public detail::lock_base_unique<Mutex> { | |
private: | |
using base = detail::lock_base_unique<Mutex>; | |
using self = unique_lock_base; | |
public: | |
using base::base; | |
void swap(self& that) noexcept { base::swap(that); } | |
friend void swap(self& a, self& b) noexcept { a.swap(b); } | |
}; | |
// shared_lock_base | |
// | |
// A lock-holder base which holds shared locks, usable with any shared mutex | |
// type. | |
// | |
// Works with both shared-lockable mutex types and shared-lockable-with-state | |
// mutex types. | |
// | |
// When defining shared-lockable-with-state mutex types, specialize | |
// std::shared_lock to derive this. See the example with upgrade_lock. | |
// | |
// A shared-lockable-with-state mutex type is signalled by the return type of | |
// mutex member function lock_shared. Members try_lock_shared, | |
// try_lock_shared_for, and try_lock_shared_until all return this type and | |
// member unlock_shared accepts this type. Likewise for mutex member | |
// transition functions. | |
template <typename Mutex> | |
class shared_lock_base : public detail::lock_base_shared<Mutex> { | |
private: | |
using base = detail::lock_base_shared<Mutex>; | |
using self = shared_lock_base; | |
public: | |
using base::base; | |
void swap(self& that) noexcept { base::swap(that); } | |
friend void swap(self& a, self& b) noexcept { a.swap(b); } | |
}; | |
// upgrade_lock_base | |
// | |
// A lock-holder base which holds upgrade locks, usable with any upgrade mutex | |
// type. | |
// | |
// Works with both upgrade-lockable mutex types and upgrade-lockable-with-state | |
// mutex types. | |
// | |
// There are no use-cases except the one below. | |
// | |
// An upgrade-lockable-with-state mutex type is signalled by the return type of | |
// mutex member function lock_upgrade. Members try_lock_upgrade, | |
// try_lock_upgrade_for, and try_lock_upgrade_until all return this type and | |
// member unlock_upgrade accepts this type. Likewise for mutex member | |
// transition functions. | |
template <typename Mutex> | |
class upgrade_lock_base : public detail::lock_base_upgrade<Mutex> { | |
private: | |
using base = detail::lock_base_upgrade<Mutex>; | |
using self = upgrade_lock_base; | |
public: | |
using base::base; | |
void swap(self& that) noexcept { base::swap(that); } | |
friend void swap(self& a, self& b) noexcept { a.swap(b); } | |
}; | |
// hybrid_lock_base | |
// | |
// A lock-holder base which holds shared locks for shared mutex types or | |
// exclusive locks otherwise. | |
// | |
// See unique_lock_base and shared_lock_base. | |
template <typename Mutex> | |
class hybrid_lock_base : public detail::lock_base_hybrid<Mutex> { | |
private: | |
using base = detail::lock_base_hybrid<Mutex>; | |
using self = hybrid_lock_base; | |
public: | |
using base::base; | |
void swap(self& that) noexcept { base::swap(that); } | |
friend void swap(self& a, self& b) noexcept { a.swap(b); } | |
}; | |
// unique_lock | |
// | |
// Alias to std::unique_lock. | |
using std::unique_lock; | |
// shared_lock | |
// | |
// Alias to std::shared_lock. | |
using std::shared_lock; | |
// upgrade_lock | |
// | |
// A lock-holder type which holds upgrade locks, usable with any upgrade mutex | |
// type. An upgrade mutex is a shared mutex which supports the upgrade state. | |
// | |
// Works with both upgrade-lockable mutex types and upgrade-lockable-with-state | |
// mutex types. | |
// | |
// Upgrade locks are not useful by themselves; they are primarily useful since | |
// upgrade locks may be transitioned atomically to exclusive locks. This lock- | |
// holder type works with the transition_to_... functions below to facilitate | |
// atomic transition from ugprade lock to exclusive lock. | |
template <typename Mutex> | |
class upgrade_lock : public upgrade_lock_base<Mutex> { | |
public: | |
using upgrade_lock_base<Mutex>::upgrade_lock_base; | |
}; | |
// hybrid_lock | |
// | |
// A lock-holder type which holds shared locks for shared mutex types or | |
// exclusive locks otherwise. | |
// | |
// See unique_lock and shared_lock. | |
template <typename Mutex> | |
class hybrid_lock : public hybrid_lock_base<Mutex> { | |
public: | |
using hybrid_lock_base<Mutex>::hybrid_lock_base; | |
}; | |
#if __cpp_deduction_guides >= 201611 | |
template <typename Mutex, typename... A> | |
explicit hybrid_lock(Mutex&, A const&...) -> hybrid_lock<Mutex>; | |
#endif | |
// lock_guard_base | |
// | |
// A lock-guard which holds exclusive locks, usable with any mutex type. | |
// | |
// Works with both lockable mutex types and lockable-with-state mutex types. | |
// | |
// When defining lockable-with-state mutex types, specialize std::lock_guard | |
// to derive this. | |
template <typename Mutex> | |
class unique_lock_guard_base | |
: public detail::lock_guard_base<Mutex, detail::lock_policy_unique> { | |
private: | |
using base = detail::lock_guard_base<Mutex, detail::lock_policy_unique>; | |
public: | |
using base::base; | |
}; | |
// unique_lock_guard | |
// | |
// Alias to std::lock_guard. | |
template <typename Mutex> | |
using unique_lock_guard = std::lock_guard<Mutex>; | |
// shared_lock_guard | |
// | |
// A lock-guard which holds shared locks, usable with any shared mutex type. | |
// | |
// Works with both lockable mutex types and lockable-with-state mutex types. | |
template <typename Mutex> | |
class shared_lock_guard | |
: public detail::lock_guard_base<Mutex, detail::lock_policy_shared> { | |
private: | |
using base = detail::lock_guard_base<Mutex, detail::lock_policy_shared>; | |
public: | |
using base::base; | |
}; | |
// hybrid_lock_guard | |
// | |
// For shared mutex types, effectively shared_lock_guard; otherwise, | |
// effectively unique_lock_guard. | |
template <typename Mutex> | |
class hybrid_lock_guard | |
: public detail::lock_guard_base<Mutex, detail::lock_policy_hybrid<Mutex>> { | |
private: | |
using base = | |
detail::lock_guard_base<Mutex, detail::lock_policy_hybrid<Mutex>>; | |
public: | |
using base::base; | |
}; | |
#if __cpp_deduction_guides >= 201611 | |
template <typename Mutex, typename... A> | |
explicit hybrid_lock_guard(Mutex&, A const&...) -> hybrid_lock_guard<Mutex>; | |
#endif | |
// make_unique_lock | |
// | |
// Returns a unique_lock constructed with the given arguments. Deduces the | |
// mutex type. | |
struct make_unique_lock_fn { | |
template <typename Mutex, typename... A> | |
unique_lock<Mutex> operator()(Mutex& mutex, A&&... a) const { | |
return unique_lock<Mutex>{mutex, static_cast<A&&>(a)...}; | |
} | |
}; | |
inline constexpr make_unique_lock_fn make_unique_lock{}; | |
// make_shared_lock | |
// | |
// Returns a shared_lock constructed with the given arguments. Deduces the | |
// mutex type. | |
struct make_shared_lock_fn { | |
template <typename Mutex, typename... A> | |
shared_lock<Mutex> operator()(Mutex& mutex, A&&... a) const { | |
return shared_lock<Mutex>{mutex, static_cast<A&&>(a)...}; | |
} | |
}; | |
inline constexpr make_shared_lock_fn make_shared_lock{}; | |
// make_upgrade_lock | |
// | |
// Returns an upgrade_lock constructed with the given arguments. Deduces the | |
// mutex type. | |
struct make_upgrade_lock_fn { | |
template <typename Mutex, typename... A> | |
upgrade_lock<Mutex> operator()(Mutex& mutex, A&&... a) const { | |
return upgrade_lock<Mutex>{mutex, static_cast<A&&>(a)...}; | |
} | |
}; | |
inline constexpr make_upgrade_lock_fn make_upgrade_lock{}; | |
// make_hybrid_lock | |
// | |
// Returns a hybrid_lock constructed with the given arguments. Deduces the | |
// mutex type. | |
struct make_hybrid_lock_fn { | |
template <typename Mutex, typename... A> | |
hybrid_lock<Mutex> operator()(Mutex& mutex, A&&... a) const { | |
return hybrid_lock<Mutex>{mutex, static_cast<A&&>(a)...}; | |
} | |
}; | |
inline constexpr make_hybrid_lock_fn make_hybrid_lock{}; | |
namespace detail { | |
template <typename L> | |
using lock_state_type_of_t_ = typename L::state_type; | |
template <typename L> | |
using lock_state_type_of_t = detected_or_t<void, lock_state_type_of_t_, L>; | |
template <typename State> | |
struct transition_lock_result_ { | |
template <typename Transition, typename Mutex, typename... A> | |
using apply = std::invoke_result_t<Transition, Mutex&, State const&, A const&...>; | |
}; | |
template <> | |
struct transition_lock_result_<void> { | |
template <typename Transition, typename Mutex, typename... A> | |
using apply = std::invoke_result_t<Transition, Mutex&, A const&...>; | |
}; | |
template <typename From, typename Transition, typename... A> | |
using transition_lock_result_t_ = | |
typename transition_lock_result_<lock_state_type_of_t<From>>:: | |
template apply<Transition, typename From::mutex_type&, A...>; | |
template < | |
typename From, | |
typename Transition, | |
typename... A, | |
typename FromState = lock_state_type_of_t<From>, | |
std::enable_if_t<std::is_void<FromState>::value, int> = 0> | |
auto transition_lock_2_(From& lock, Transition transition, A const&... a) { | |
// release() may check or mutate mutex state to support the dissociation, call | |
// it before performing the transition. | |
return transition(*lock.release(), a...); | |
} | |
template < | |
typename From, | |
typename Transition, | |
typename... A, | |
typename FromState = lock_state_type_of_t<From>, | |
std::enable_if_t<!std::is_void<FromState>::value, int> = 0> | |
auto transition_lock_2_(From& lock, Transition transition, A const&... a) { | |
auto state = lock.state(); | |
// release() may check or mutate mutex state to support the dissociation, call | |
// it before performing the transition. | |
return transition(*lock.release(), std::move(state), a...); | |
} | |
template < | |
typename From, | |
typename Transition, | |
typename... A, | |
typename Result = transition_lock_result_t_<From, Transition, A...>, | |
std::enable_if_t<std::is_void<Result>::value, int> = 0> | |
auto transition_lock_1_(From& lock, Transition transition, A const&... a) { | |
return detail::transition_lock_2_(lock, transition, a...), true; | |
} | |
template < | |
typename From, | |
typename Transition, | |
typename... A, | |
typename Result = transition_lock_result_t_<From, Transition, A...>, | |
std::enable_if_t<!std::is_void<Result>::value, int> = 0> | |
auto transition_lock_1_(From& lock, Transition transition, A const&... a) { | |
return detail::transition_lock_2_(lock, transition, a...); | |
} | |
template < | |
typename To, | |
typename From, | |
typename Transition, | |
typename... A, | |
typename ToState = lock_state_type_of_t<To>, | |
std::enable_if_t<std::is_void<ToState>::value, int> = 0> | |
auto transition_lock_0_(From& lock, Transition transition, A const&... a) { | |
auto& mutex = *lock.mutex(); | |
auto s = detail::transition_lock_1_(lock, transition, a...); | |
return !s ? To{} : To{mutex, std::adopt_lock}; | |
} | |
template < | |
typename To, | |
typename From, | |
typename Transition, | |
typename... A, | |
typename ToState = lock_state_type_of_t<To>, | |
std::enable_if_t<!std::is_void<ToState>::value, int> = 0> | |
auto transition_lock_0_(From& lock, Transition transition, A const&... a) { | |
auto& mutex = *lock.mutex(); | |
auto s = detail::transition_lock_1_(lock, transition, a...); | |
return !s ? To{} : To{mutex, std::adopt_lock, s}; | |
} | |
template < | |
template <typename> | |
class To, | |
template <typename> | |
class From, | |
typename Mutex, | |
typename Transition, | |
typename... A> | |
auto transition_lock_(From<Mutex>& lock, Transition transition, A const&... a) { | |
// clang-format off | |
return | |
!lock.mutex() ? To<Mutex>{} : | |
!lock.owns_lock() ? To<Mutex>{*lock.release(), std::defer_lock} : | |
detail::transition_lock_0_<To<Mutex>>(lock, transition, a...); | |
// clang-format on | |
} | |
template <typename, typename> | |
struct transition_lock_policy; | |
template <typename Mutex> | |
struct transition_lock_policy<unique_lock<Mutex>, shared_lock<Mutex>> { | |
using transition_fn = access::unlock_and_lock_shared_fn; | |
}; | |
template <typename Mutex> | |
struct transition_lock_policy<unique_lock<Mutex>, upgrade_lock<Mutex>> { | |
using transition_fn = access::unlock_and_lock_upgrade_fn; | |
}; | |
template <typename Mutex> | |
struct transition_lock_policy<shared_lock<Mutex>, unique_lock<Mutex>> { | |
using try_transition_fn = access::try_unlock_shared_and_lock_fn; | |
using try_transition_for_fn = access::try_unlock_shared_and_lock_for_fn; | |
using try_transition_until_fn = access::try_unlock_shared_and_lock_until_fn; | |
}; | |
template <typename Mutex> | |
struct transition_lock_policy<shared_lock<Mutex>, upgrade_lock<Mutex>> { | |
using try_transition_fn = access::try_unlock_shared_and_lock_upgrade_fn; | |
using try_transition_for_fn = | |
access::try_unlock_shared_and_lock_upgrade_for_fn; | |
using try_transition_until_fn = | |
access::try_unlock_shared_and_lock_upgrade_until_fn; | |
}; | |
template <typename Mutex> | |
struct transition_lock_policy<upgrade_lock<Mutex>, unique_lock<Mutex>> { | |
using transition_fn = access::unlock_upgrade_and_lock_fn; | |
using try_transition_fn = access::try_unlock_upgrade_and_lock_fn; | |
using try_transition_for_fn = access::try_unlock_upgrade_and_lock_for_fn; | |
using try_transition_until_fn = access::try_unlock_upgrade_and_lock_until_fn; | |
}; | |
template <typename Mutex> | |
struct transition_lock_policy<upgrade_lock<Mutex>, shared_lock<Mutex>> { | |
using transition_fn = access::unlock_upgrade_and_lock_shared_fn; | |
}; | |
} // namespace detail | |
// transition_lock | |
// | |
// Represents an atomic transition from the from-lock to the to-lock. Waits | |
// unboundedly for the transition to become available. | |
template < | |
template <typename> | |
class ToLock, | |
typename Mutex, | |
template <typename> | |
class FromLock> | |
ToLock<Mutex> transition_lock(FromLock<Mutex>& lock) { | |
using policy = detail::transition_lock_policy<FromLock<Mutex>, ToLock<Mutex>>; | |
auto _ = typename policy::transition_fn{}; | |
return detail::transition_lock_<ToLock>(lock, _); | |
} | |
// try_transition_lock | |
// | |
// Represents an atomic transition attempt from the from-lock to the to-lock. | |
// Does not wait if the transition is not immediately available. | |
template < | |
template <typename> | |
class ToLock, | |
typename Mutex, | |
template <typename> | |
class FromLock> | |
ToLock<Mutex> try_transition_lock(FromLock<Mutex>& lock) { | |
using policy = detail::transition_lock_policy<FromLock<Mutex>, ToLock<Mutex>>; | |
auto _ = typename policy::try_transition_fn{}; | |
return detail::transition_lock_<ToLock>(lock, _); | |
} | |
// try_transition_lock_for | |
// | |
// Represents an atomic transition attempt from the from-lock to the to-lock | |
// bounded by a timeout. Waits up to the timeout for the transition to become | |
// available. | |
template < | |
template <typename> | |
class ToLock, | |
typename Mutex, | |
template <typename> | |
class FromLock, | |
typename Rep, | |
typename Period> | |
ToLock<Mutex> try_transition_lock_for( | |
FromLock<Mutex>& lock, std::chrono::duration<Rep, Period> const& timeout) { | |
using policy = detail::transition_lock_policy<FromLock<Mutex>, ToLock<Mutex>>; | |
auto _ = typename policy::try_transition_for_fn{}; | |
return detail::transition_lock_<ToLock>(lock, _, timeout); | |
} | |
// try_transition_lock_until | |
// | |
// Represents an atomic transition attempt from the from-lock to the to-lock | |
// bounded by a deadline. Waits up to the deadline for the transition to become | |
// available. | |
template < | |
template <typename> | |
class ToLock, | |
typename Mutex, | |
template <typename> | |
class FromLock, | |
typename Clock, | |
typename Duration> | |
ToLock<Mutex> try_transition_lock_until( | |
FromLock<Mutex>& lock, | |
std::chrono::time_point<Clock, Duration> const& deadline) { | |
using policy = detail::transition_lock_policy<FromLock<Mutex>, ToLock<Mutex>>; | |
auto _ = typename policy::try_transition_until_fn{}; | |
return detail::transition_lock_<ToLock>(lock, _, deadline); | |
} | |
// transition_to_shared_lock(unique_lock) | |
// | |
// Wraps mutex member function unlock_and_lock_shared. | |
// | |
// Represents an immediate atomic downgrade transition from exclusive lock to | |
// to shared lock. | |
template <typename Mutex> | |
shared_lock<Mutex> transition_to_shared_lock(unique_lock<Mutex>& lock) { | |
return transition_lock<shared_lock>(lock); | |
} | |
// transition_to_shared_lock(upgrade_lock) | |
// | |
// Wraps mutex member function unlock_upgrade_and_lock_shared. | |
// | |
// Represents an immediate atomic downgrade transition from upgrade lock to | |
// shared lock. | |
template <typename Mutex> | |
shared_lock<Mutex> transition_to_shared_lock(upgrade_lock<Mutex>& lock) { | |
return transition_lock<shared_lock>(lock); | |
} | |
// transition_to_upgrade_lock(unique_lock) | |
// | |
// Wraps mutex member function unlock_and_lock_upgrade. | |
// | |
// Represents an immediate atomic downgrade transition from unique lock to | |
// upgrade lock. | |
template <typename Mutex> | |
upgrade_lock<Mutex> transition_to_upgrade_lock(unique_lock<Mutex>& lock) { | |
return transition_lock<upgrade_lock>(lock); | |
} | |
// transition_to_unique_lock(upgrade_lock) | |
// | |
// Wraps mutex member function unlock_upgrade_and_lock. | |
// | |
// Represents an eventual atomic upgrade transition from upgrade lock to unique | |
// lock. | |
template <typename Mutex> | |
unique_lock<Mutex> transition_to_unique_lock(upgrade_lock<Mutex>& lock) { | |
return transition_lock<unique_lock>(lock); | |
} | |
// try_transition_to_unique_lock(upgrade_lock) | |
// | |
// Wraps mutex member function try_unlock_upgrade_and_lock. | |
// | |
// Represents an immediate attempted atomic upgrade transition from upgrade | |
// lock to unique lock. | |
template <typename Mutex> | |
unique_lock<Mutex> try_transition_to_unique_lock(upgrade_lock<Mutex>& lock) { | |
return transition_lock<unique_lock>(lock); | |
} | |
// try_transition_to_unique_lock_for(upgrade_lock) | |
// | |
// Wraps mutex member function try_unlock_upgrade_and_lock_for. | |
// | |
// Represents an eventual attempted atomic upgrade transition from upgrade | |
// lock to unique lock. | |
template <typename Mutex, typename Rep, typename Period> | |
unique_lock<Mutex> try_transition_to_unique_lock_for( | |
upgrade_lock<Mutex>& lock, | |
std::chrono::duration<Rep, Period> const& timeout) { | |
return try_transition_lock_for<unique_lock>(lock, timeout); | |
} | |
// try_transition_to_unique_lock_until(upgrade_lock) | |
// | |
// Wraps mutex member function try_unlock_upgrade_and_lock_until. | |
// | |
// Represents an eventual attempted atomic upgrade transition from upgrade | |
// lock to unique lock. | |
template <typename Mutex, typename Clock, typename Duration> | |
unique_lock<Mutex> try_transition_to_unique_lock_until( | |
upgrade_lock<Mutex>& lock, | |
std::chrono::time_point<Clock, Duration> const& deadline) { | |
return try_transition_lock_until<unique_lock>(lock, deadline); | |
} | |
// try_transition_to_unique_lock(shared_lock) | |
// | |
// Wraps mutex member function try_unlock_shared_and_lock. | |
// | |
// Represents an immediate attempted atomic upgrade transition from shared | |
// lock to unique lock. | |
template <typename Mutex> | |
unique_lock<Mutex> try_transition_to_unique_lock(shared_lock<Mutex>& lock) { | |
return try_transition_lock<unique_lock>(lock); | |
} | |
// try_transition_to_unique_lock_for(shared_lock) | |
// | |
// Wraps mutex member function try_unlock_shared_and_lock_for. | |
// | |
// Represents an eventual attempted atomic upgrade transition from shared | |
// lock to unique lock. | |
template <typename Mutex, typename Rep, typename Period> | |
unique_lock<Mutex> try_transition_to_unique_lock_for( | |
shared_lock<Mutex>& lock, | |
std::chrono::duration<Rep, Period> const& timeout) { | |
return try_transition_lock_for<unique_lock>(lock, timeout); | |
} | |
// try_transition_to_unique_lock_until(shared_lock) | |
// | |
// Wraps mutex member function try_unlock_shared_and_lock_until. | |
// | |
// Represents an eventual attempted atomic upgrade transition from shared | |
// lock to unique lock. | |
template <typename Mutex, typename Clock, typename Duration> | |
unique_lock<Mutex> try_transition_to_unique_lock_until( | |
shared_lock<Mutex>& lock, | |
std::chrono::time_point<Clock, Duration> const& deadline) { | |
return try_transition_lock_until<unique_lock>(lock, deadline); | |
} | |
// try_transition_to_upgrade_lock(shared_lock) | |
// | |
// Wraps mutex member function try_unlock_shared_and_lock_upgrade. | |
// | |
// Represents an immediate attempted atomic upgrade transition from shared | |
// lock to upgrade lock. | |
template <typename Mutex> | |
upgrade_lock<Mutex> try_transition_to_upgrade_lock(shared_lock<Mutex>& lock) { | |
return try_transition_lock<upgrade_lock>(lock); | |
} | |
// try_transition_to_upgrade_lock_for(shared_lock) | |
// | |
// Wraps mutex member function try_unlock_shared_and_lock_upgrade_for. | |
// | |
// Represents an eventual attempted atomic upgrade transition from shared | |
// lock to upgrade lock. | |
template <typename Mutex, typename Rep, typename Period> | |
upgrade_lock<Mutex> try_transition_to_upgrade_lock_for( | |
shared_lock<Mutex>& lock, | |
std::chrono::duration<Rep, Period> const& timeout) { | |
return try_transition_lock_for<upgrade_lock>(lock, timeout); | |
} | |
// try_transition_to_upgrade_lock_until(shared_lock) | |
// | |
// Wraps mutex member function try_unlock_shared_and_lock_upgrade_until. | |
// | |
// Represents an eventual attempted atomic upgrade transition from shared | |
// lock to upgrade lock. | |
template <typename Mutex, typename Clock, typename Duration> | |
upgrade_lock<Mutex> try_transition_to_upgrade_lock_until( | |
shared_lock<Mutex>& lock, | |
std::chrono::time_point<Clock, Duration> const& deadline) { | |
return try_transition_lock_until<upgrade_lock>(lock, deadline); | |
} | |
} // namespace folly |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) Meta Platforms, Inc. and 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. | |
*/ | |
// | |
// Docs: https://fburl.com/fbcref_synchronized | |
// | |
/** | |
* This module implements a Synchronized abstraction useful in | |
* mutex-based concurrency. | |
* | |
* The Synchronized<T, Mutex> class is the primary public API exposed by this | |
* module. See folly/docs/Synchronized.md for a more complete explanation of | |
* this class and its benefits. | |
*/ | |
#pragma once | |
#include <array> | |
#include <mutex> | |
#include <shared_mutex> | |
#include <tuple> | |
#include <type_traits> | |
#include <utility> | |
#include "lock.hpp" | |
namespace folly { | |
namespace detail { | |
template <typename, typename Mutex> | |
inline constexpr bool kSynchronizedMutexIsUnique = false; | |
template <typename Mutex> | |
inline constexpr bool kSynchronizedMutexIsUnique< | |
decltype(void(std::declval<Mutex&>().lock())), | |
Mutex> = true; | |
template <typename, typename Mutex> | |
inline constexpr bool kSynchronizedMutexIsShared = false; | |
template <typename Mutex> | |
inline constexpr bool kSynchronizedMutexIsShared< | |
decltype(void(std::declval<Mutex&>().lock_shared())), | |
Mutex> = true; | |
template <typename, typename Mutex> | |
inline constexpr bool kSynchronizedMutexIsUpgrade = false; | |
template <typename Mutex> | |
inline constexpr bool kSynchronizedMutexIsUpgrade< | |
decltype(void(std::declval<Mutex&>().lock_upgrade())), | |
Mutex> = true; | |
/** | |
* 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 SynchronizedMutexLevel { Unknown, Unique, Shared, Upgrade }; | |
template <typename Mutex> | |
inline constexpr SynchronizedMutexLevel kSynchronizedMutexLevel = | |
kSynchronizedMutexIsUpgrade<void, Mutex> ? SynchronizedMutexLevel::Upgrade | |
: kSynchronizedMutexIsShared<void, Mutex> ? SynchronizedMutexLevel::Shared | |
: kSynchronizedMutexIsUnique<void, Mutex> ? SynchronizedMutexLevel::Unique | |
: SynchronizedMutexLevel::Unknown; | |
enum class SynchronizedMutexMethod { Lock, TryLock }; | |
template <SynchronizedMutexLevel Level, SynchronizedMutexMethod Method> | |
struct SynchronizedLockPolicy { | |
static constexpr SynchronizedMutexLevel level = Level; | |
static constexpr SynchronizedMutexMethod method = Method; | |
}; | |
using SynchronizedLockPolicyExclusive = SynchronizedLockPolicy< | |
SynchronizedMutexLevel::Unique, | |
SynchronizedMutexMethod::Lock>; | |
using SynchronizedLockPolicyTryExclusive = SynchronizedLockPolicy< | |
SynchronizedMutexLevel::Unique, | |
SynchronizedMutexMethod::TryLock>; | |
using SynchronizedLockPolicyShared = SynchronizedLockPolicy< | |
SynchronizedMutexLevel::Shared, | |
SynchronizedMutexMethod::Lock>; | |
using SynchronizedLockPolicyTryShared = SynchronizedLockPolicy< | |
SynchronizedMutexLevel::Shared, | |
SynchronizedMutexMethod::TryLock>; | |
using SynchronizedLockPolicyUpgrade = SynchronizedLockPolicy< | |
SynchronizedMutexLevel::Upgrade, | |
SynchronizedMutexMethod::Lock>; | |
using SynchronizedLockPolicyTryUpgrade = SynchronizedLockPolicy< | |
SynchronizedMutexLevel::Upgrade, | |
SynchronizedMutexMethod::TryLock>; | |
template <SynchronizedMutexLevel> | |
struct SynchronizedLockType_ {}; | |
template <> | |
struct SynchronizedLockType_<SynchronizedMutexLevel::Unique> { | |
template <typename Mutex> | |
using apply = std::unique_lock<Mutex>; | |
}; | |
template <> | |
struct SynchronizedLockType_<SynchronizedMutexLevel::Shared> { | |
template <typename Mutex> | |
using apply = std::shared_lock<Mutex>; | |
}; | |
template <> | |
struct SynchronizedLockType_<SynchronizedMutexLevel::Upgrade> { | |
template <typename Mutex> | |
using apply = upgrade_lock<Mutex>; | |
}; | |
template <SynchronizedMutexLevel Level, typename MutexType> | |
using SynchronizedLockType = | |
typename SynchronizedLockType_<Level>::template apply<MutexType>; | |
} // namespace detail | |
/** | |
* SynchronizedBase is a helper parent class for Synchronized<T>. | |
* | |
* It provides wlock() and rlock() methods for shared mutex types, | |
* or lock() methods for purely exclusive mutex types. | |
*/ | |
template <class Subclass, detail::SynchronizedMutexLevel level> | |
class SynchronizedBase; | |
template <class LockedType, class Mutex, class LockPolicy> | |
class LockedPtrBase; | |
template <class LockedType, class LockPolicy> | |
class LockedPtr; | |
/** | |
* SynchronizedBase specialization for shared mutex types. | |
* | |
* This class provides wlock() and rlock() methods for acquiring the lock and | |
* accessing the data. | |
*/ | |
template <class Subclass> | |
class SynchronizedBase<Subclass, detail::SynchronizedMutexLevel::Shared> { | |
private: | |
template <typename T, typename P> | |
using LockedPtr_ = ::folly::LockedPtr<T, P>; | |
public: | |
using LockPolicyExclusive = detail::SynchronizedLockPolicyExclusive; | |
using LockPolicyShared = detail::SynchronizedLockPolicyShared; | |
using LockPolicyTryExclusive = detail::SynchronizedLockPolicyTryExclusive; | |
using LockPolicyTryShared = detail::SynchronizedLockPolicyTryShared; | |
using WLockedPtr = LockedPtr_<Subclass, LockPolicyExclusive>; | |
using ConstWLockedPtr = LockedPtr_<const Subclass, LockPolicyExclusive>; | |
using RLockedPtr = LockedPtr_<Subclass, LockPolicyShared>; | |
using ConstRLockedPtr = LockedPtr_<const Subclass, LockPolicyShared>; | |
using TryWLockedPtr = LockedPtr_<Subclass, LockPolicyTryExclusive>; | |
using ConstTryWLockedPtr = LockedPtr_<const Subclass, LockPolicyTryExclusive>; | |
using TryRLockedPtr = LockedPtr_<Subclass, LockPolicyTryShared>; | |
using ConstTryRLockedPtr = LockedPtr_<const Subclass, LockPolicyTryShared>; | |
// These aliases are deprecated. | |
// TODO: Codemod them away. | |
using LockedPtr = WLockedPtr; | |
using ConstLockedPtr = ConstRLockedPtr; | |
/** | |
* @brief Acquire an exclusive lock. | |
* | |
* Acquire an exclusive lock, and return a LockedPtr that can be used to | |
* safely access the datum. | |
* | |
* LockedPtr offers operator -> and * to provide access to the datum. | |
* The lock will be released when the LockedPtr is destroyed. | |
* | |
* @methodset Exclusive lock | |
*/ | |
LockedPtr wlock() { return LockedPtr(static_cast<Subclass*>(this)); } | |
ConstWLockedPtr wlock() const { | |
return ConstWLockedPtr(static_cast<const Subclass*>(this)); | |
} | |
/** | |
* @brief Acquire an exclusive lock, or null. | |
* | |
* Attempts to acquire the lock in exclusive mode. If acquisition is | |
* unsuccessful, the returned LockedPtr will be null. | |
* | |
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for | |
* validity.) | |
* | |
* @methodset Exclusive lock | |
*/ | |
TryWLockedPtr tryWLock() { | |
return TryWLockedPtr{static_cast<Subclass*>(this)}; | |
} | |
ConstTryWLockedPtr tryWLock() const { | |
return ConstTryWLockedPtr{static_cast<const Subclass*>(this)}; | |
} | |
/** | |
* @brief Acquire a read lock. | |
* | |
* The returned LockedPtr will force const access to the data unless the lock | |
* is acquired in non-const context and asNonConstUnsafe() is used. | |
* | |
* @methodset Shared lock | |
*/ | |
RLockedPtr rlock() { return RLockedPtr(static_cast<Subclass*>(this)); } | |
ConstLockedPtr rlock() const { | |
return ConstLockedPtr(static_cast<const Subclass*>(this)); | |
} | |
/** | |
* @brief Acquire a read lock, or null. | |
* | |
* Attempts to acquire the lock in shared mode. If acquisition is | |
* unsuccessful, the returned LockedPtr will be null. | |
* | |
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for | |
* validity.) | |
* | |
* @methodset Shared lock | |
*/ | |
TryRLockedPtr tryRLock() { | |
return TryRLockedPtr{static_cast<Subclass*>(this)}; | |
} | |
ConstTryRLockedPtr tryRLock() const { | |
return ConstTryRLockedPtr{static_cast<const Subclass*>(this)}; | |
} | |
/** | |
* Attempts to acquire the lock, or fails if the timeout elapses first. | |
* If acquisition is unsuccessful, the returned LockedPtr will be null. | |
* | |
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for | |
* validity.) | |
* | |
* @methodset Exclusive lock | |
*/ | |
template <class Rep, class Period> | |
LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) { | |
return LockedPtr(static_cast<Subclass*>(this), timeout); | |
} | |
template <class Rep, class Period> | |
LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) const { | |
return LockedPtr(static_cast<const Subclass*>(this), timeout); | |
} | |
/** | |
* Attempts to acquire the lock, or fails if the timeout elapses first. | |
* If acquisition is unsuccessful, the returned LockedPtr will be null. | |
* | |
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for | |
* validity.) | |
* | |
* @methodset Shared lock | |
*/ | |
template <class Rep, class Period> | |
RLockedPtr rlock(const std::chrono::duration<Rep, Period>& timeout) { | |
return RLockedPtr(static_cast<Subclass*>(this), timeout); | |
} | |
template <class Rep, class Period> | |
ConstRLockedPtr rlock( | |
const std::chrono::duration<Rep, Period>& timeout) const { | |
return ConstRLockedPtr(static_cast<const Subclass*>(this), timeout); | |
} | |
/** | |
* Invoke a function while holding the lock exclusively. | |
* | |
* A reference to the datum will be passed into the function as its only | |
* argument. | |
* | |
* This can be used with a lambda argument for easily defining small critical | |
* sections in the code. For example: | |
* | |
* auto value = obj.withWLock([](auto& data) { | |
* data.doStuff(); | |
* return data.getValue(); | |
* }); | |
* | |
* @methodset Exclusive lock | |
*/ | |
template <class Function> | |
auto withWLock(Function&& function) { | |
return function(*wlock()); | |
} | |
template <class Function> | |
auto withWLock(Function&& function) const { | |
return function(*wlock()); | |
} | |
/** | |
* Invoke a function while holding the lock exclusively. | |
* | |
* This is similar to withWLock(), but the function will be passed a | |
* LockedPtr rather than a reference to the data itself. | |
* | |
* This allows scopedUnlock() to be called on the LockedPtr argument if | |
* desired. | |
* | |
* @methodset Exclusive lock | |
*/ | |
template <class Function> | |
auto withWLockPtr(Function&& function) { | |
return function(wlock()); | |
} | |
template <class Function> | |
auto withWLockPtr(Function&& function) const { | |
return function(wlock()); | |
} | |
/** | |
* Invoke a function while holding an the lock in shared mode. | |
* | |
* A const reference to the datum will be passed into the function as its | |
* only argument. | |
* | |
* @methodset Shared lock | |
*/ | |
template <class Function> | |
auto withRLock(Function&& function) const { | |
return function(*rlock()); | |
} | |
/** | |
* Invoke a function while holding the lock in shared mode. | |
* | |
* This is similar to withRLock(), but the function will be passed a | |
* LockedPtr rather than a reference to the data itself. | |
* | |
* This allows scopedUnlock() to be called on the LockedPtr argument if | |
* desired. | |
* | |
* @methodset Shared lock | |
*/ | |
template <class Function> | |
auto withRLockPtr(Function&& function) { | |
return function(rlock()); | |
} | |
template <class Function> | |
auto withRLockPtr(Function&& function) const { | |
return function(rlock()); | |
} | |
}; | |
/** | |
* SynchronizedBase specialization for upgrade mutex types. | |
* | |
* This class provides all the functionality provided by the SynchronizedBase | |
* specialization for shared mutexes and a ulock() method that returns an | |
* upgrade lock RAII proxy | |
*/ | |
template <class Subclass> | |
class SynchronizedBase<Subclass, detail::SynchronizedMutexLevel::Upgrade> | |
: public SynchronizedBase< | |
Subclass, | |
detail::SynchronizedMutexLevel::Shared> { | |
private: | |
template <typename T, typename P> | |
using LockedPtr_ = ::folly::LockedPtr<T, P>; | |
public: | |
using LockPolicyUpgrade = detail::SynchronizedLockPolicyUpgrade; | |
using LockPolicyTryUpgrade = detail::SynchronizedLockPolicyTryUpgrade; | |
using UpgradeLockedPtr = LockedPtr_<Subclass, LockPolicyUpgrade>; | |
using ConstUpgradeLockedPtr = LockedPtr_<const Subclass, LockPolicyUpgrade>; | |
using TryUpgradeLockedPtr = LockedPtr_<Subclass, LockPolicyTryUpgrade>; | |
using ConstTryUpgradeLockedPtr = | |
LockedPtr_<const Subclass, LockPolicyTryUpgrade>; | |
/** | |
* @brief Acquire an upgrade lock. | |
* | |
* The returned LockedPtr will have force const access to the data unless the | |
* lock is acquired in non-const context and asNonConstUnsafe() is used. | |
* | |
* @methodset Upgrade lock | |
*/ | |
UpgradeLockedPtr ulock() { | |
return UpgradeLockedPtr(static_cast<Subclass*>(this)); | |
} | |
ConstUpgradeLockedPtr ulock() const { | |
return ConstUpgradeLockedPtr(static_cast<const Subclass*>(this)); | |
} | |
/** | |
* @brief Acquire an upgrade lock, or null. | |
* | |
* Attempts to acquire the lock in upgrade mode. If acquisition is | |
* unsuccessful, the returned LockedPtr will be null. | |
* | |
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for | |
* validity.) | |
* | |
* @methodset Upgrade lock | |
*/ | |
TryUpgradeLockedPtr tryULock() { | |
return TryUpgradeLockedPtr{static_cast<Subclass*>(this)}; | |
} | |
/** | |
* Acquire an upgrade lock and return a LockedPtr that can be used to safely | |
* access the datum | |
* | |
* And the const version | |
* | |
* @methodset Upgrade lock | |
*/ | |
template <class Rep, class Period> | |
UpgradeLockedPtr ulock(const std::chrono::duration<Rep, Period>& timeout) { | |
return UpgradeLockedPtr(static_cast<Subclass*>(this), timeout); | |
} | |
/** | |
* Invoke a function while holding the lock. | |
* | |
* A reference to the datum will be passed into the function as its only | |
* argument. | |
* | |
* This can be used with a lambda argument for easily defining small critical | |
* sections in the code. For example: | |
* | |
* auto value = obj.withULock([](auto& data) { | |
* data.doStuff(); | |
* return data.getValue(); | |
* }); | |
* | |
* This is probably not the function you want. If the intent is to read the | |
* data object and determine whether you should upgrade to a write lock then | |
* the withULockPtr() method should be called instead, since it gives access | |
* to the LockedPtr proxy (which can be upgraded via the | |
* moveFromUpgradeToWrite() method) | |
* | |
* @methodset Upgrade lock | |
*/ | |
template <class Function> | |
auto withULock(Function&& function) { | |
return function(*ulock()); | |
} | |
template <class Function> | |
auto withULock(Function&& function) const { | |
return function(*ulock()); | |
} | |
/** | |
* Invoke a function while holding the lock exclusively. | |
* | |
* This is similar to withULock(), but the function will be passed a | |
* LockedPtr rather than a reference to the data itself. | |
* | |
* This allows scopedUnlock() and as_lock() to be called on the | |
* LockedPtr argument. | |
* | |
* This also allows you to upgrade the LockedPtr proxy to a write state so | |
* that changes can be made to the underlying data | |
* | |
* @methodset Upgrade lock | |
*/ | |
template <class Function> | |
auto withULockPtr(Function&& function) { | |
return function(ulock()); | |
} | |
template <class Function> | |
auto withULockPtr(Function&& function) const { | |
return function(ulock()); | |
} | |
}; | |
/** | |
* SynchronizedBase specialization for non-shared mutex types. | |
* | |
* This class provides lock() methods for acquiring the lock and accessing the | |
* data. | |
*/ | |
template <class Subclass> | |
class SynchronizedBase<Subclass, detail::SynchronizedMutexLevel::Unique> { | |
private: | |
template <typename T, typename P> | |
using LockedPtr_ = ::folly::LockedPtr<T, P>; | |
public: | |
using LockPolicyExclusive = detail::SynchronizedLockPolicyExclusive; | |
using LockPolicyTryExclusive = detail::SynchronizedLockPolicyTryExclusive; | |
using LockedPtr = LockedPtr_<Subclass, LockPolicyExclusive>; | |
using ConstLockedPtr = LockedPtr_<const Subclass, LockPolicyExclusive>; | |
using TryLockedPtr = LockedPtr_<Subclass, LockPolicyTryExclusive>; | |
using ConstTryLockedPtr = LockedPtr_<const Subclass, LockPolicyTryExclusive>; | |
/** | |
* @brief Acquire the lock. | |
* | |
* Return a LockedPtr that can be used to safely access the datum. | |
* | |
* @methodset Non-shareable lock | |
*/ | |
LockedPtr lock() { return LockedPtr(static_cast<Subclass*>(this)); } | |
/** | |
* Acquire a lock, and return a ConstLockedPtr that can be used to safely | |
* access the datum. | |
* | |
* @methodset Non-shareable lock | |
*/ | |
ConstLockedPtr lock() const { | |
return ConstLockedPtr(static_cast<const Subclass*>(this)); | |
} | |
/** | |
* @brief Acquire the lock, or null. | |
* | |
* Attempts to acquire the lock in exclusive mode. If acquisition is | |
* unsuccessful, the returned LockedPtr will be null. | |
* | |
* (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for | |
* validity.) | |
* | |
* @methodset Non-shareable lock | |
*/ | |
TryLockedPtr tryLock() { return TryLockedPtr{static_cast<Subclass*>(this)}; } | |
ConstTryLockedPtr tryLock() const { | |
return ConstTryLockedPtr{static_cast<const Subclass*>(this)}; | |
} | |
/** | |
* Attempts to acquire the lock, or fails if the timeout elapses first. | |
* If acquisition is unsuccessful, the returned LockedPtr will be null. | |
* | |
* @methodset Non-shareable lock | |
*/ | |
template <class Rep, class Period> | |
LockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) { | |
return LockedPtr(static_cast<Subclass*>(this), timeout); | |
} | |
/** | |
* Attempts to acquire the lock, or fails if the timeout elapses first. | |
* If acquisition is unsuccessful, the returned LockedPtr will be null. | |
* | |
* @methodset Non-shareable lock | |
*/ | |
template <class Rep, class Period> | |
ConstLockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) const { | |
return ConstLockedPtr(static_cast<const Subclass*>(this), timeout); | |
} | |
/** | |
* Invoke a function while holding the lock. | |
* | |
* A reference to the datum will be passed into the function as its only | |
* argument. | |
* | |
* This can be used with a lambda argument for easily defining small critical | |
* sections in the code. For example: | |
* | |
* auto value = obj.withLock([](auto& data) { | |
* data.doStuff(); | |
* return data.getValue(); | |
* }); | |
* | |
* @methodset Non-shareable lock | |
*/ | |
template <class Function> | |
auto withLock(Function&& function) { | |
return function(*lock()); | |
} | |
template <class Function> | |
auto withLock(Function&& function) const { | |
return function(*lock()); | |
} | |
/** | |
* Invoke a function while holding the lock exclusively. | |
* | |
* This is similar to withWLock(), but the function will be passed a | |
* LockedPtr rather than a reference to the data itself. | |
* | |
* This allows scopedUnlock() and as_lock() to be called on the | |
* LockedPtr argument. | |
* | |
* @methodset Non-shareable lock | |
*/ | |
template <class Function> | |
auto withLockPtr(Function&& function) { | |
return function(lock()); | |
} | |
template <class Function> | |
auto withLockPtr(Function&& function) const { | |
return function(lock()); | |
} | |
}; | |
/** | |
* `folly::Synchronized` pairs a datum with a mutex. The datum can only be | |
* reached through a `LockedPtr`, typically acquired via `.rlock()` or | |
* `.wlock()`; the mutex is held for the lifetime of the `LockedPtr`. | |
* | |
* It is recommended to explicitly open a new nested scope when aquiring | |
* a `LockedPtr` object, to help visibly delineate the critical section and to | |
* ensure that the `LockedPtr` is destroyed as soon as it is no longer needed. | |
* | |
* @tparam T The type of datum to be stored. | |
* @tparam Mutex The mutex type that guards the datum. Must be Lockable. | |
* | |
* @refcode folly/docs/examples/folly/Synchronized.cpp | |
*/ | |
template <class T, class Mutex = std::shared_mutex> | |
struct Synchronized : public SynchronizedBase< | |
Synchronized<T, Mutex>, | |
detail::kSynchronizedMutexLevel<Mutex>> { | |
private: | |
using Base = SynchronizedBase< | |
Synchronized<T, Mutex>, | |
detail::kSynchronizedMutexLevel<Mutex>>; | |
static constexpr bool nxCopyCtor{ | |
std::is_nothrow_copy_constructible<T>::value}; | |
static constexpr bool nxMoveCtor{ | |
std::is_nothrow_move_constructible<T>::value}; | |
// used to disable copy construction and assignment | |
class NonImplementedType; | |
public: | |
using LockedPtr = typename Base::LockedPtr; | |
using ConstLockedPtr = typename Base::ConstLockedPtr; | |
using DataType = T; | |
using MutexType = Mutex; | |
/** | |
* Default constructor leaves both members call their own default constructor. | |
*/ | |
constexpr Synchronized() = default; | |
public: | |
/** | |
* Copy constructor. Enabled only when the data type is copy-constructible. | |
* | |
* Takes a shared-or-exclusive lock on the source mutex while performing the | |
* copy-construction of the destination data from the source data. No lock is | |
* taken on the destination mutex. | |
* | |
* May throw even when the data type is is nothrow-copy-constructible because | |
* acquiring a lock may throw. | |
* | |
* deprecated | |
*/ | |
/* implicit */ Synchronized(typename std::conditional< | |
std::is_copy_constructible<T>::value, | |
const Synchronized&, | |
NonImplementedType>::type rhs) /* may throw */ | |
: Synchronized(rhs.copy()) {} | |
/** | |
* Move-constructs from the source data without locking either the source or | |
* the destination mutex. | |
* | |
* Semantically, assumes that the source object is a true rvalue and therefore | |
* that no synchronization is required for accessing it. | |
* | |
* deprecated | |
*/ | |
Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor) | |
: Synchronized(std::move(rhs.datum_)) {} | |
/** | |
* Constructor taking a datum as argument copies it. There is no | |
* need to lock the constructing object. | |
*/ | |
explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {} | |
/** | |
* Constructor taking a datum rvalue as argument moves it. There is no need | |
* to lock the constructing object. | |
*/ | |
explicit Synchronized(T&& rhs) noexcept(nxMoveCtor) | |
: datum_(std::move(rhs)) {} | |
/** | |
* Lets you construct non-movable types in-place. Use the constexpr | |
* instance `in_place` as the first argument. | |
*/ | |
template <typename... Args> | |
explicit constexpr Synchronized(std::in_place_t, Args&&... args) | |
: datum_(std::forward<Args>(args)...) {} | |
/** | |
* Lets you construct the synchronized object and also pass construction | |
* parameters to the underlying mutex if desired | |
*/ | |
template <typename... DatumArgs, typename... MutexArgs> | |
Synchronized( | |
std::piecewise_construct_t, | |
std::tuple<DatumArgs...> datumArgs, | |
std::tuple<MutexArgs...> mutexArgs) | |
: Synchronized{ | |
std::piecewise_construct, | |
std::move(datumArgs), | |
std::move(mutexArgs), | |
std::make_index_sequence<sizeof...(DatumArgs)>{}, | |
std::make_index_sequence<sizeof...(MutexArgs)>{}} {} | |
/** | |
* Copy assignment operator. | |
* | |
* Enabled only when the data type is copy-constructible and move-assignable. | |
* | |
* Move-assigns from a copy of the source data. | |
* | |
* Takes a shared-or-exclusive lock on the source mutex while copying the | |
* source data to a temporary. Takes an exclusive lock on the destination | |
* mutex while move-assigning from the temporary. | |
* | |
* This technique consts an extra temporary but avoids the need to take locks | |
* on both mutexes together. | |
* | |
* deprecated | |
*/ | |
Synchronized& operator=(typename std::conditional< | |
std::is_copy_constructible<T>::value && | |
std::is_move_assignable<T>::value, | |
const Synchronized&, | |
NonImplementedType>::type rhs) { | |
return *this = rhs.copy(); | |
} | |
/** | |
* Move assignment operator. | |
* | |
* Takes an exclusive lock on the destination mutex while move-assigning the | |
* destination data from the source data. The source mutex is not locked or | |
* otherwise accessed. | |
* | |
* Semantically, assumes that the source object is a true rvalue and therefore | |
* that no synchronization is required for accessing it. | |
* | |
* deprecated | |
*/ | |
Synchronized& operator=(Synchronized&& rhs) { | |
return *this = std::move(rhs.datum_); | |
} | |
/** | |
* Lock object, assign datum. | |
*/ | |
Synchronized& operator=(const T& rhs) { | |
if (&datum_ != &rhs) { | |
auto guard = LockedPtr{this}; | |
datum_ = rhs; | |
} | |
return *this; | |
} | |
/** | |
* Lock object, move-assign datum. | |
*/ | |
Synchronized& operator=(T&& rhs) { | |
if (&datum_ != &rhs) { | |
auto guard = LockedPtr{this}; | |
datum_ = std::move(rhs); | |
} | |
return *this; | |
} | |
/** | |
* @brief Acquire some lock. | |
* | |
* If the mutex is a shared mutex, and the Synchronized instance is const, | |
* this acquires a shared lock. Otherwise this acquires an exclusive lock. | |
* | |
* In general, prefer using the explicit rlock() and wlock() methods | |
* for read-write locks, and lock() for purely exclusive locks. | |
* | |
* contextualLock() is primarily intended for use in other template functions | |
* that do not necessarily know the lock type. | |
*/ | |
LockedPtr contextualLock() { return LockedPtr(this); } | |
ConstLockedPtr contextualLock() const { return ConstLockedPtr(this); } | |
template <class Rep, class Period> | |
LockedPtr contextualLock(const std::chrono::duration<Rep, Period>& timeout) { | |
return LockedPtr(this, timeout); | |
} | |
template <class Rep, class Period> | |
ConstLockedPtr contextualLock( | |
const std::chrono::duration<Rep, Period>& timeout) const { | |
return ConstLockedPtr(this, timeout); | |
} | |
/** | |
* @brief Acquire a lock for reading. | |
* | |
* contextualRLock() acquires a read lock if the mutex type is shared, | |
* or a regular exclusive lock for non-shared mutex types. | |
* | |
* contextualRLock() when you know that you prefer a read lock (if | |
* available), even if the Synchronized<T> object itself is non-const. | |
*/ | |
ConstLockedPtr contextualRLock() const { return ConstLockedPtr(this); } | |
template <class Rep, class Period> | |
ConstLockedPtr contextualRLock( | |
const std::chrono::duration<Rep, Period>& timeout) const { | |
return ConstLockedPtr(this, timeout); | |
} | |
/** | |
* @brief Access the datum under lock. | |
* | |
* deprecated | |
* | |
* This accessor offers a LockedPtr. In turn, LockedPtr offers | |
* operator-> returning a pointer to T. The operator-> keeps | |
* expanding until it reaches a pointer, so syncobj->foo() will lock | |
* the object and call foo() against it. | |
* | |
* NOTE: This API is planned to be deprecated in an upcoming diff. | |
* Prefer using lock(), wlock(), or rlock() instead. | |
*/ | |
[[deprecated("use explicit lock(), wlock(), or rlock() instead")]] LockedPtr | |
operator->() { | |
return LockedPtr(this); | |
} | |
/** | |
* deprecated | |
* | |
* Obtain a ConstLockedPtr. | |
* | |
* NOTE: This API is planned to be deprecated in an upcoming diff. | |
* Prefer using lock(), wlock(), or rlock() instead. | |
*/ | |
[[deprecated( | |
"use explicit lock(), wlock(), or rlock() instead")]] ConstLockedPtr | |
operator->() const { | |
return ConstLockedPtr(this); | |
} | |
/** | |
* @brief Acquire a LockedPtr with timeout. | |
* | |
* Attempts to acquire for a given number of milliseconds. If | |
* acquisition is unsuccessful, the returned LockedPtr is nullptr. | |
* | |
* NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead. | |
* In the future it will be marked with a deprecation attribute to emit | |
* build-time warnings, and then it will be removed entirely. | |
*/ | |
LockedPtr timedAcquire(unsigned int milliseconds) { | |
return LockedPtr(this, std::chrono::milliseconds(milliseconds)); | |
} | |
/** | |
* Attempts to acquire for a given number of milliseconds. If | |
* acquisition is unsuccessful, the returned ConstLockedPtr is nullptr. | |
* | |
* NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead. | |
* In the future it will be marked with a deprecation attribute to emit | |
* build-time warnings, and then it will be removed entirely. | |
*/ | |
ConstLockedPtr timedAcquire(unsigned int milliseconds) const { | |
return ConstLockedPtr(this, std::chrono::milliseconds(milliseconds)); | |
} | |
/** | |
* @brief Swap datum. | |
* | |
* Swaps with another Synchronized. Protected against | |
* self-swap. Only data is swapped. Locks are acquired in increasing | |
* address order. | |
*/ | |
void swap(Synchronized& rhs) { | |
if (this == &rhs) { | |
return; | |
} | |
if (this > &rhs) { | |
return rhs.swap(*this); | |
} | |
auto guard1 = LockedPtr{this}; | |
auto guard2 = LockedPtr{&rhs}; | |
using std::swap; | |
swap(datum_, rhs.datum_); | |
} | |
/** | |
* Swap with another datum. Recommended because it keeps the mutex | |
* held only briefly. | |
*/ | |
void swap(T& rhs) { | |
LockedPtr guard(this); | |
using std::swap; | |
swap(datum_, rhs); | |
} | |
/** | |
* @brief Exchange datum. | |
* | |
* Assign another datum and return the original value. Recommended | |
* because it keeps the mutex held only briefly. | |
*/ | |
T exchange(T&& rhs) { | |
swap(rhs); | |
return std::move(rhs); | |
} | |
/** | |
* Copies datum to a given target. | |
*/ | |
void copyInto(T& target) const { | |
ConstLockedPtr guard(this); | |
target = datum_; | |
} | |
/** | |
* Returns a fresh copy of the datum. | |
*/ | |
T copy() const { | |
ConstLockedPtr guard(this); | |
return datum_; | |
} | |
/** | |
* @brief Access datum without locking. | |
* | |
* Returns a reference to the datum without acquiring a lock. | |
* | |
* Provided as a backdoor for call-sites where it is known safe to be used. | |
* For example, when it is known that only one thread has access to the | |
* Synchronized instance. | |
* | |
* To be used with care - this method explicitly overrides the normal safety | |
* guarantees provided by the rest of the Synchronized API. | |
*/ | |
T& unsafeGetUnlocked() { return datum_; } | |
const T& unsafeGetUnlocked() const { return datum_; } | |
private: | |
template <class LockedType, class MutexType, class LockPolicy> | |
friend class folly::LockedPtrBase; | |
template <class LockedType, class LockPolicy> | |
friend class folly::LockedPtr; | |
/** | |
* Helper constructors to enable Synchronized for | |
* non-default constructible types T. | |
* Guards are created in actual public constructors and are alive | |
* for the time required to construct the object | |
*/ | |
Synchronized( | |
const Synchronized& rhs, | |
const ConstLockedPtr& /*guard*/) noexcept(nxCopyCtor) | |
: datum_(rhs.datum_) {} | |
Synchronized(Synchronized&& rhs, const LockedPtr& /*guard*/) noexcept( | |
nxMoveCtor) | |
: datum_(std::move(rhs.datum_)) {} | |
template < | |
typename... DatumArgs, | |
typename... MutexArgs, | |
std::size_t... IndicesOne, | |
std::size_t... IndicesTwo> | |
Synchronized( | |
std::piecewise_construct_t, | |
std::tuple<DatumArgs...> datumArgs, | |
std::tuple<MutexArgs...> mutexArgs, | |
std::index_sequence<IndicesOne...>, | |
std::index_sequence<IndicesTwo...>) | |
: datum_{std::get<IndicesOne>(std::move(datumArgs))...}, | |
mutex_{std::get<IndicesTwo>(std::move(mutexArgs))...} {} | |
// simulacrum of data members - keep data members in sync! | |
// LockedPtr needs offsetof() which is specified only for standard-layout | |
// types which Synchronized is not so we define a simulacrum for offsetof | |
struct Simulacrum { | |
std::aligned_storage_t<sizeof(DataType), alignof(DataType)> datum_; | |
std::aligned_storage_t<sizeof(MutexType), alignof(MutexType)> mutex_; | |
}; | |
// data members - keep simulacrum of data members in sync! | |
T datum_; | |
mutable Mutex mutex_; | |
}; | |
/** | |
* Deprecated subclass of Synchronized that provides implicit locking | |
* via operator->. This is intended to ease migration while preventing | |
* accidental use of operator-> in new code. | |
*/ | |
template <class T, class Mutex = std::shared_mutex> | |
struct [[deprecated( | |
"use Synchronized and explicit lock(), wlock(), or rlock() instead")]] ImplicitSynchronized | |
: Synchronized<T, Mutex> { | |
private: | |
using Base = Synchronized<T, Mutex>; | |
public: | |
using LockedPtr = typename Base::LockedPtr; | |
using ConstLockedPtr = typename Base::ConstLockedPtr; | |
using DataType = typename Base::DataType; | |
using MutexType = typename Base::MutexType; | |
using Base::Base; | |
using Base::operator=; | |
}; | |
template <class SynchronizedType, class LockPolicy> | |
class ScopedUnlocker; | |
namespace detail { | |
/* | |
* A helper alias that resolves to "const T" if the template parameter | |
* is a const Synchronized<T>, or "T" if the parameter is not const. | |
*/ | |
template <class SynchronizedType, bool AllowsConcurrentAccess> | |
using SynchronizedDataType = typename std::conditional< | |
AllowsConcurrentAccess || std::is_const<SynchronizedType>::value, | |
typename SynchronizedType::DataType const, | |
typename SynchronizedType::DataType>::type; | |
/* | |
* A helper alias that resolves to a ConstLockedPtr if the template parameter | |
* is a const Synchronized<T>, or a LockedPtr if the parameter is not const. | |
*/ | |
template <class SynchronizedType> | |
using LockedPtrType = typename std::conditional< | |
std::is_const<SynchronizedType>::value, | |
typename SynchronizedType::ConstLockedPtr, | |
typename SynchronizedType::LockedPtr>::type; | |
template < | |
typename Synchronized, | |
typename LockFunc, | |
typename TryLockFunc, | |
typename... Args> | |
class SynchronizedLocker { | |
public: | |
using LockedPtr = std::invoke_result_t<LockFunc&, Synchronized&, const Args&...>; | |
template <typename LockFuncType, typename TryLockFuncType, typename... As> | |
SynchronizedLocker( | |
Synchronized& sync, | |
LockFuncType&& lockFunc, | |
TryLockFuncType tryLockFunc, | |
As&&... as) | |
: synchronized{sync}, | |
lockFunc_{std::forward<LockFuncType>(lockFunc)}, | |
tryLockFunc_{std::forward<TryLockFuncType>(tryLockFunc)}, | |
args_{std::forward<As>(as)...} {} | |
auto lock() const { | |
auto args = std::tuple<const Args&...>{args_}; | |
return apply(lockFunc_, std::tuple_cat(std::tie(synchronized), args)); | |
} | |
auto tryLock() const { return tryLockFunc_(synchronized); } | |
private: | |
Synchronized& synchronized; | |
LockFunc lockFunc_; | |
TryLockFunc tryLockFunc_; | |
std::tuple<Args...> args_; | |
}; | |
template < | |
typename Synchronized, | |
typename LockFunc, | |
typename TryLockFunc, | |
typename... Args> | |
auto makeSynchronizedLocker( | |
Synchronized& synchronized, | |
LockFunc&& lockFunc, | |
TryLockFunc&& tryLockFunc, | |
Args&&... args) { | |
using LockFuncType = std::decay_t<LockFunc>; | |
using TryLockFuncType = std::decay_t<TryLockFunc>; | |
return SynchronizedLocker< | |
Synchronized, | |
LockFuncType, | |
TryLockFuncType, | |
std::decay_t<Args>...>{ | |
synchronized, | |
std::forward<LockFunc>(lockFunc), | |
std::forward<TryLockFunc>(tryLockFunc), | |
std::forward<Args>(args)...}; | |
} | |
/** | |
* Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe | |
* manner. | |
* | |
* The function uses the "smart and polite" algorithm from this link | |
* http://howardhinnant.github.io/dining_philosophers.html#Polite | |
* | |
* The gist of the algorithm is that it locks a mutex, then tries to lock the | |
* other mutexes in a non-blocking manner. If all the locks succeed, we are | |
* done, if not, we release the locks we have held, yield to allow other | |
* threads to continue and then block on the mutex that we failed to acquire. | |
* | |
* This allows dynamically yielding ownership of all the mutexes but one, so | |
* that other threads can continue doing work and locking the other mutexes. | |
* See the benchmarks in folly/test/SynchronizedBenchmark.cpp for more. | |
*/ | |
template <typename... SynchronizedLocker> | |
auto lock(SynchronizedLocker... lockersIn) | |
-> std::tuple<typename SynchronizedLocker::LockedPtr...> { | |
// capture the list of lockers as a tuple | |
auto lockers = std::forward_as_tuple(lockersIn...); | |
// make a list of null LockedPtr instances that we will return to the caller | |
auto lockedPtrs = std::tuple<typename SynchronizedLocker::LockedPtr...>{}; | |
// start by locking the first thing in the list | |
std::get<0>(lockedPtrs) = std::get<0>(lockers).lock(); | |
auto indexLocked = 0; | |
while (true) { | |
auto couldLockAll = true; | |
auto index = 0; | |
for (auto &locker : lockers) | |
{ | |
// if we should try_lock on the current locker then do so | |
if (index != indexLocked) { | |
auto lockedPtr = locker.tryLock(); | |
// if we were unable to lock this mutex, | |
// | |
// 1. release all the locks, | |
// 2. yield control to another thread to be nice | |
// 3. block on the mutex we failed to lock, acquire the lock | |
// 4. break out and set the index of the current mutex to indicate | |
// which mutex we have locked | |
if (!lockedPtr) { | |
// writing lockedPtrs = decltype(lockedPtrs){} does not compile on | |
// gcc, I believe this is a bug D7676798 | |
lockedPtrs = std::tuple<typename SynchronizedLocker::LockedPtr...>{}; | |
std::this_thread::yield(); | |
fetch(lockedPtrs, index) = locker.lock(); | |
indexLocked = index; | |
couldLockAll = false; | |
break; | |
} | |
// else store the locked mutex in the list we return | |
fetch(lockedPtrs, index) = std::move(lockedPtr); | |
} | |
index++; | |
} | |
if (couldLockAll) { | |
return lockedPtrs; | |
} | |
} | |
} | |
template <typename Synchronized, typename... Args> | |
auto wlock(Synchronized& synchronized, Args&&... args) { | |
return detail::makeSynchronizedLocker( | |
synchronized, | |
[](auto& s, auto&&... a) { | |
return s.wlock(std::forward<decltype(a)>(a)...); | |
}, | |
[](auto& s) { return s.tryWLock(); }, | |
std::forward<Args>(args)...); | |
} | |
template <typename Synchronized, typename... Args> | |
auto rlock(Synchronized& synchronized, Args&&... args) { | |
return detail::makeSynchronizedLocker( | |
synchronized, | |
[](auto& s, auto&&... a) { | |
return s.rlock(std::forward<decltype(a)>(a)...); | |
}, | |
[](auto& s) { return s.tryRLock(); }, | |
std::forward<Args>(args)...); | |
} | |
template <typename Synchronized, typename... Args> | |
auto ulock(Synchronized& synchronized, Args&&... args) { | |
return detail::makeSynchronizedLocker( | |
synchronized, | |
[](auto& s, auto&&... a) { | |
return s.ulock(std::forward<decltype(a)>(a)...); | |
}, | |
[](auto& s) { return s.tryULock(); }, | |
std::forward<Args>(args)...); | |
} | |
template <typename Synchronized, typename... Args> | |
auto lock(Synchronized& synchronized, Args&&... args) { | |
return detail::makeSynchronizedLocker( | |
synchronized, | |
[](auto& s, auto&&... a) { | |
return s.lock(std::forward<decltype(a)>(a)...); | |
}, | |
[](auto& s) { return s.tryLock(); }, | |
std::forward<Args>(args)...); | |
} | |
} // namespace detail | |
/** | |
* This class temporarily unlocks a LockedPtr in a scoped manner. | |
*/ | |
template <class SynchronizedType, class LockPolicy> | |
class ScopedUnlocker { | |
public: | |
explicit ScopedUnlocker(LockedPtr<SynchronizedType, LockPolicy>* p) noexcept | |
: ptr_(p), parent_(p->parent()) { | |
ptr_->releaseLock(); | |
} | |
ScopedUnlocker(const ScopedUnlocker&) = delete; | |
ScopedUnlocker& operator=(const ScopedUnlocker&) = delete; | |
ScopedUnlocker(ScopedUnlocker&& other) noexcept | |
: ptr_(std::exchange(other.ptr_, nullptr)), | |
parent_(std::exchange(other.parent_, nullptr)) {} | |
ScopedUnlocker& operator=(ScopedUnlocker&& other) = delete; | |
~ScopedUnlocker() noexcept(false) { | |
if (ptr_) { | |
ptr_->reacquireLock(parent_); | |
} | |
} | |
private: | |
LockedPtr<SynchronizedType, LockPolicy>* ptr_{nullptr}; | |
SynchronizedType* parent_{nullptr}; | |
}; | |
/** | |
* A LockedPtr keeps a Synchronized<T> object locked for the duration of | |
* LockedPtr's existence. | |
* | |
* It provides access the datum's members directly by using operator->() and | |
* operator*(). | |
* | |
* The LockPolicy parameter controls whether or not the lock is acquired in | |
* exclusive or shared mode. | |
*/ | |
template <class SynchronizedType, class LockPolicy> | |
class LockedPtr { | |
private: | |
constexpr static bool AllowsConcurrentAccess = | |
LockPolicy::level != detail::SynchronizedMutexLevel::Unique; | |
using CDataType = // the DataType with the appropriate const-qualification | |
detail::SynchronizedDataType<SynchronizedType, AllowsConcurrentAccess>; | |
template <typename LockPolicyOther> | |
using EnableIfSameLevel = | |
std::enable_if_t<LockPolicy::level == LockPolicyOther::level>; | |
template <typename, typename> | |
friend class LockedPtr; | |
friend class ScopedUnlocker<SynchronizedType, LockPolicy>; | |
public: | |
using DataType = typename SynchronizedType::DataType; | |
using MutexType = typename SynchronizedType::MutexType; | |
using Synchronized = typename std::remove_const<SynchronizedType>::type; | |
using LockType = detail::SynchronizedLockType<LockPolicy::level, MutexType>; | |
/** | |
* Creates an uninitialized LockedPtr. | |
* | |
* Dereferencing an uninitialized LockedPtr is not allowed. | |
*/ | |
LockedPtr() = default; | |
/** | |
* Takes a Synchronized<T> and locks it. | |
*/ | |
explicit LockedPtr(SynchronizedType* parent) | |
: lock_{!parent ? LockType{} : doLock(parent->mutex_)} {} | |
/** | |
* Takes a Synchronized<T> and attempts to lock it, within the specified | |
* timeout. | |
* | |
* Blocks until the lock is acquired or until the specified timeout expires. | |
* If the timeout expired without acquiring the lock, the LockedPtr will be | |
* null, and LockedPtr::isNull() will return true. | |
*/ | |
template <class Rep, class Period> | |
LockedPtr( | |
SynchronizedType* parent, | |
const std::chrono::duration<Rep, Period>& timeout) | |
: lock_{parent ? LockType{parent->mutex_, timeout} : LockType{}} {} | |
/** | |
* Move constructor. | |
*/ | |
LockedPtr(LockedPtr&& rhs) noexcept = default; | |
template < | |
typename Type = SynchronizedType, | |
std::enable_if_t<std::is_const<Type>::value, int> = 0> | |
/* implicit */ LockedPtr(LockedPtr<Synchronized, LockPolicy>&& rhs) noexcept | |
: lock_{std::move(rhs.lock_)} {} | |
template < | |
typename LockPolicyType, | |
EnableIfSameLevel<LockPolicyType>* = nullptr> | |
explicit LockedPtr( | |
LockedPtr<SynchronizedType, LockPolicyType>&& other) noexcept | |
: lock_{std::move(other.lock_)} {} | |
template < | |
typename Type = SynchronizedType, | |
typename LockPolicyType, | |
std::enable_if_t<std::is_const<Type>::value, int> = 0, | |
EnableIfSameLevel<LockPolicyType>* = nullptr> | |
explicit LockedPtr(LockedPtr<Synchronized, LockPolicyType>&& rhs) noexcept | |
: lock_{std::move(rhs.lock_)} {} | |
/** | |
* Move assignment operator. | |
*/ | |
LockedPtr& operator=(LockedPtr&& rhs) noexcept = default; | |
template < | |
typename LockPolicyType, | |
EnableIfSameLevel<LockPolicyType>* = nullptr> | |
LockedPtr& operator=( | |
LockedPtr<SynchronizedType, LockPolicyType>&& other) noexcept { | |
lock_ = std::move(other.lock_); | |
return *this; | |
} | |
template < | |
typename Type = SynchronizedType, | |
typename LockPolicyType, | |
std::enable_if_t<std::is_const<Type>::value, int> = 0, | |
EnableIfSameLevel<LockPolicyType>* = nullptr> | |
LockedPtr& operator=( | |
LockedPtr<Synchronized, LockPolicyType>&& other) noexcept { | |
lock_ = std::move(other.lock_); | |
return *this; | |
} | |
/* | |
* Copy constructor and assignment operator are deleted. | |
*/ | |
LockedPtr(const LockedPtr& rhs) = delete; | |
LockedPtr& operator=(const LockedPtr& rhs) = delete; | |
/** | |
* Destructor releases. | |
*/ | |
~LockedPtr() = default; | |
/** | |
* Access the underlying lock object. | |
*/ | |
LockType& as_lock() noexcept { return lock_; } | |
LockType const& as_lock() const noexcept { return lock_; } | |
/** | |
* Check if this LockedPtr is uninitialized, or points to valid locked data. | |
* | |
* This method can be used to check if a timed-acquire operation succeeded. | |
* If an acquire operation times out it will result in a null LockedPtr. | |
* | |
* A LockedPtr is always either null, or holds a lock to valid data. | |
* Methods such as scopedUnlock() reset the LockedPtr to null for the | |
* duration of the unlock. | |
*/ | |
bool isNull() const { return !lock_.owns_lock(); } | |
/** | |
* Explicit boolean conversion. | |
* | |
* Returns !isNull() | |
*/ | |
explicit operator bool() const { return lock_.owns_lock(); } | |
/** | |
* Access the locked data. | |
* | |
* This method should only be used if the LockedPtr is valid. | |
*/ | |
CDataType* operator->() const { return std::addressof(parent()->datum_); } | |
/** | |
* Access the locked data. | |
* | |
* This method should only be used if the LockedPtr is valid. | |
*/ | |
CDataType& operator*() const { return parent()->datum_; } | |
void unlock() noexcept { lock_ = {}; } | |
/** | |
* Locks that allow concurrent access (shared, upgrade) force const | |
* access with the standard accessors even if the Synchronized | |
* object is non-const. | |
* | |
* In some cases non-const access can be needed, for example: | |
* | |
* - Under an upgrade lock, to get references that will be mutated | |
* after upgrading to a write lock. | |
* | |
* - Under an read lock, if some mutating operations on the data | |
* are thread safe (e.g. mutating the value in an associative | |
* container with reference stability). | |
* | |
* asNonConstUnsafe() returns a non-const reference to the data if | |
* the parent Synchronized object was non-const at the point of lock | |
* acquisition. | |
*/ | |
template <typename = void> | |
DataType& asNonConstUnsafe() const { | |
static_assert( | |
AllowsConcurrentAccess && !std::is_const<SynchronizedType>::value, | |
"asNonConstUnsafe() is only available on non-exclusive locks" | |
" acquired in a non-const context"); | |
return parent()->datum_; | |
} | |
/** | |
* Temporarily unlock the LockedPtr, and reset it to null. | |
* | |
* Returns an helper object that will re-lock and restore the LockedPtr when | |
* the helper is destroyed. The LockedPtr may not be dereferenced for as | |
* long as this helper object exists. | |
*/ | |
ScopedUnlocker<SynchronizedType, LockPolicy> scopedUnlock() { | |
return ScopedUnlocker<SynchronizedType, LockPolicy>(this); | |
} | |
/*************************************************************************** | |
* Upgrade lock methods. | |
* These are disabled via SFINAE when the mutex is not an upgrade mutex. | |
**************************************************************************/ | |
/** | |
* Move the locked ptr from an upgrade state to an exclusive state. The | |
* current lock is left in a null state. | |
*/ | |
template < | |
typename SyncType = SynchronizedType, | |
decltype(void(std::declval<typename SyncType::MutexType&>() | |
.lock_upgrade()))* = nullptr> | |
LockedPtr<SynchronizedType, detail::SynchronizedLockPolicyExclusive> | |
moveFromUpgradeToWrite() { | |
static_assert(std::is_same<SyncType, SynchronizedType>::value, "mismatch"); | |
return transition_to_unique_lock(lock_); | |
} | |
/** | |
* Move the locked ptr from an exclusive state to an upgrade state. The | |
* current lock is left in a null state. | |
*/ | |
template < | |
typename SyncType = SynchronizedType, | |
decltype(void(std::declval<typename SyncType::MutexType&>() | |
.lock_upgrade()))* = nullptr> | |
LockedPtr<SynchronizedType, detail::SynchronizedLockPolicyUpgrade> | |
moveFromWriteToUpgrade() { | |
static_assert(std::is_same<SyncType, SynchronizedType>::value, "mismatch"); | |
return transition_to_upgrade_lock(lock_); | |
} | |
/** | |
* Move the locked ptr from an upgrade state to a shared state. The | |
* current lock is left in a null state. | |
*/ | |
template < | |
typename SyncType = SynchronizedType, | |
decltype(void(std::declval<typename SyncType::MutexType&>() | |
.lock_upgrade()))* = nullptr> | |
LockedPtr<SynchronizedType, detail::SynchronizedLockPolicyShared> | |
moveFromUpgradeToRead() { | |
static_assert(std::is_same<SyncType, SynchronizedType>::value, "mismatch"); | |
return transition_to_shared_lock(lock_); | |
} | |
/** | |
* Move the locked ptr from an exclusive state to a shared state. The | |
* current lock is left in a null state. | |
*/ | |
template < | |
typename SyncType = SynchronizedType, | |
decltype(void(std::declval<typename SyncType::MutexType&>() | |
.lock_shared()))* = nullptr> | |
LockedPtr<SynchronizedType, detail::SynchronizedLockPolicyShared> | |
moveFromWriteToRead() { | |
static_assert(std::is_same<SyncType, SynchronizedType>::value, "mismatch"); | |
return transition_to_shared_lock(lock_); | |
} | |
SynchronizedType* parent() const { | |
using simulacrum = typename SynchronizedType::Simulacrum; | |
static_assert(sizeof(simulacrum) == sizeof(SynchronizedType), "mismatch"); | |
static_assert(alignof(simulacrum) == alignof(SynchronizedType), "mismatch"); | |
constexpr auto off = offsetof(simulacrum, mutex_); | |
const auto raw = reinterpret_cast<char*>(lock_.mutex()); | |
return reinterpret_cast<SynchronizedType*>(raw - (raw ? off : 0)); | |
} | |
private: | |
/* implicit */ LockedPtr(LockType lock) noexcept : lock_{std::move(lock)} {} | |
template <typename LP> | |
static constexpr bool is_try = | |
LP::method == detail::SynchronizedMutexMethod::TryLock; | |
template < | |
typename MT, | |
typename LT = LockType, | |
typename LP = LockPolicy, | |
std::enable_if_t<is_try<LP>, int> = 0> | |
inline static LT doLock(MT& mutex) { | |
return LT{mutex, std::try_to_lock}; | |
} | |
template < | |
typename MT, | |
typename LT = LockType, | |
typename LP = LockPolicy, | |
std::enable_if_t<!is_try<LP>, int> = 0> | |
inline static LT doLock(MT& mutex) { | |
return LT{mutex}; | |
} | |
void releaseLock() noexcept { | |
DCHECK(lock_.owns_lock()); | |
lock_ = {}; | |
} | |
void reacquireLock(SynchronizedType* parent) { | |
DCHECK(parent); | |
DCHECK(!lock_.owns_lock()); | |
lock_ = doLock(parent->mutex_); | |
} | |
LockType lock_; | |
}; | |
/** | |
* Helper functions that should be passed to either a lock() or synchronized() | |
* invocation, these return implementation defined structs that will be used | |
* to lock the synchronized instance appropriately. | |
* | |
* lock(wlock(one), rlock(two), wlock(three)); | |
* synchronized([](auto one, two) { ... }, wlock(one), rlock(two)); | |
* | |
* For example in the above rlock() produces an implementation defined read | |
* locking helper instance and wlock() a write locking helper | |
* | |
* Subsequent arguments passed to these locking helpers, after the first, will | |
* be passed by const-ref to the corresponding function on the synchronized | |
* instance. This means that if the function accepts these parameters by | |
* value, they will be copied. Note that it is not necessary that the primary | |
* locking function will be invoked at all (for eg. the implementation might | |
* just invoke the try*Lock() method) | |
* | |
* // Try to acquire the lock for one second | |
* synchronized([](auto) { ... }, wlock(one, 1s)); | |
* | |
* // The timed lock acquire might never actually be called, if it is not | |
* // needed by the underlying deadlock avoiding algorithm | |
* synchronized([](auto, auto) { ... }, rlock(one), wlock(two, 1s)); | |
* | |
* Note that the arguments passed to to *lock() calls will be passed by | |
* const-ref to the function invocation, as the implementation might use them | |
* many times | |
*/ | |
template <typename D, typename M, typename... Args> | |
auto wlock(Synchronized<D, M>& synchronized, Args&&... args) { | |
return detail::wlock(synchronized, std::forward<Args>(args)...); | |
} | |
template <typename D, typename M, typename... Args> | |
auto wlock(const Synchronized<D, M>& synchronized, Args&&... args) { | |
return detail::wlock(synchronized, std::forward<Args>(args)...); | |
} | |
template <typename Data, typename Mutex, typename... Args> | |
auto rlock(const Synchronized<Data, Mutex>& synchronized, Args&&... args) { | |
return detail::rlock(synchronized, std::forward<Args>(args)...); | |
} | |
template <typename D, typename M, typename... Args> | |
auto ulock(Synchronized<D, M>& synchronized, Args&&... args) { | |
return detail::ulock(synchronized, std::forward<Args>(args)...); | |
} | |
template <typename D, typename M, typename... Args> | |
auto lock(Synchronized<D, M>& synchronized, Args&&... args) { | |
return detail::lock(synchronized, std::forward<Args>(args)...); | |
} | |
template <typename D, typename M, typename... Args> | |
auto lock(const Synchronized<D, M>& synchronized, Args&&... args) { | |
return detail::lock(synchronized, std::forward<Args>(args)...); | |
} | |
/** | |
* Acquire locks for multiple Synchronized<> objects, in a deadlock-safe | |
* manner. | |
* | |
* Wrap the synchronized instances with the appropriate locking strategy by | |
* using one of the four strategies - folly::lock (exclusive acquire for | |
* exclusive only mutexes), folly::rlock (shared acquire for shareable | |
* mutexes), folly::wlock (exclusive acquire for shareable mutexes) or | |
* folly::ulock (upgrade acquire for upgrade mutexes) (see above) | |
* | |
* The locks will be acquired and the passed callable will be invoked with the | |
* LockedPtr instances in the order that they were passed to the function | |
*/ | |
template <typename Func, typename... SynchronizedLockers> | |
decltype(auto) synchronized(Func&& func, SynchronizedLockers&&... lockers) { | |
return apply( | |
std::forward<Func>(func), | |
lock(std::forward<SynchronizedLockers>(lockers)...)); | |
} | |
/** | |
* Acquire locks on many lockables or synchronized instances in such a way | |
* that the sequence of calls within the function does not cause deadlocks. | |
* | |
* This can often result in a performance boost as compared to simply | |
* acquiring your locks in an ordered manner. Even for very simple cases. | |
* The algorithm tried to adjust to contention by blocking on the mutex it | |
* thinks is the best fit, leaving all other mutexes open to be locked by | |
* other threads. See the benchmarks in folly/test/SynchronizedBenchmark.cpp | |
* for more | |
* | |
* This works differently as compared to the locking algorithm in libstdc++ | |
* and is the recommended way to acquire mutexes in a generic order safe | |
* manner. Performance benchmarks show that this does better than the one in | |
* libstdc++ even for the simple cases | |
* | |
* Usage is the same as std::lock() for arbitrary lockables | |
* | |
* folly::lock(one, two, three); | |
* | |
* To make it work with folly::Synchronized you have to specify how you want | |
* the locks to be acquired, use the folly::wlock(), folly::rlock(), | |
* folly::ulock() and folly::lock() helpers defined below | |
* | |
* auto [one, two] = lock(folly::wlock(a), folly::rlock(b)); | |
* | |
* Note that you can/must avoid the folly:: namespace prefix on the lock() | |
* function if you use the helpers, ADL lookup is done to find the lock function | |
* | |
* This will execute the deadlock avoidance algorithm and acquire a write lock | |
* for a and a read lock for b | |
*/ | |
template <typename LockableOne, typename LockableTwo, typename... Lockables> | |
void lock(LockableOne& one, LockableTwo& two, Lockables&... lockables) { | |
auto locker = [](auto& lockable) { | |
using Lockable = std::remove_reference_t<decltype(lockable)>; | |
return detail::makeSynchronizedLocker( | |
lockable, | |
[](auto& l) { return std::unique_lock<Lockable>{l}; }, | |
[](auto& l) { | |
auto lock = std::unique_lock<Lockable>{l, std::defer_lock}; | |
lock.try_lock(); | |
return lock; | |
}); | |
}; | |
auto locks = lock(locker(one), locker(two), locker(lockables)...); | |
// release ownership of the locks from the RAII lock wrapper returned by the | |
// function above | |
for (auto &lock : locks) | |
lock.release(); | |
} | |
/** | |
* Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe | |
* manner. | |
* | |
* The locks are acquired in order from lowest address to highest address. | |
* (Note that this is not necessarily the same algorithm used by std::lock().) | |
* For parameters that are const and support shared locks, a read lock is | |
* acquired. Otherwise an exclusive lock is acquired. | |
* | |
* use lock() with folly::wlock(), folly::rlock() and folly::ulock() for | |
* arbitrary locking without causing a deadlock (as much as possible), with the | |
* same effects as std::lock() | |
*/ | |
template <class Sync1, class Sync2> | |
std::tuple<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>> | |
acquireLocked(Sync1& l1, Sync2& l2) { | |
if (static_cast<const void*>(&l1) < static_cast<const void*>(&l2)) { | |
auto p1 = l1.contextualLock(); | |
auto p2 = l2.contextualLock(); | |
return std::make_tuple(std::move(p1), std::move(p2)); | |
} else { | |
auto p2 = l2.contextualLock(); | |
auto p1 = l1.contextualLock(); | |
return std::make_tuple(std::move(p1), std::move(p2)); | |
} | |
} | |
/** | |
* A version of acquireLocked() that returns a std::pair rather than a | |
* std::tuple, which is easier to use in many places. | |
*/ | |
template <class Sync1, class Sync2> | |
std::pair<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>> | |
acquireLockedPair(Sync1& l1, Sync2& l2) { | |
auto lockedPtrs = acquireLocked(l1, l2); | |
return { | |
std::move(std::get<0>(lockedPtrs)), std::move(std::get<1>(lockedPtrs))}; | |
} | |
/************************************************************************ | |
* NOTE: All APIs below this line will be deprecated in upcoming diffs. | |
************************************************************************/ | |
// Non-member swap primitive | |
template <class T, class M> | |
void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) { | |
lhs.swap(rhs); | |
} | |
/** | |
* Disambiguate the name var by concatenating the line number of the original | |
* point of expansion. This avoids shadowing warnings for nested | |
* SYNCHRONIZEDs. The name is consistent if used multiple times within | |
* another macro. | |
* Only for internal use. | |
*/ | |
#define SYNCHRONIZED_VAR(var) FB_CONCATENATE(SYNCHRONIZED_##var##_, __LINE__) | |
namespace detail { | |
struct [[deprecated( | |
"use explicit lock(), wlock(), or rlock() instead")]] SYNCHRONIZED_macro_is_deprecated{}; | |
} | |
/** | |
* NOTE: This API is deprecated. Use lock(), wlock(), rlock() or the withLock | |
* functions instead. In the future it will be marked with a deprecation | |
* attribute to emit build-time warnings, and then it will be removed entirely. | |
* | |
* SYNCHRONIZED is the main facility that makes Synchronized<T> | |
* helpful. It is a pseudo-statement that introduces a scope where the | |
* object is locked. Inside that scope you get to access the unadorned | |
* datum. | |
* | |
* Example: | |
* | |
* Synchronized<vector<int>> svector; | |
* ... | |
* SYNCHRONIZED (svector) { ... use svector as a vector<int> ... } | |
* or | |
* SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... } | |
* | |
* Refer to folly/docs/Synchronized.md for a detailed explanation and more | |
* examples. | |
*/ | |
#define SYNCHRONIZED(...) \ | |
FOLLY_PUSH_WARNING \ | |
FOLLY_GNU_DISABLE_WARNING("-Wshadow") \ | |
FOLLY_MSVC_DISABLE_WARNING(4189) /* initialized but unreferenced */ \ | |
FOLLY_MSVC_DISABLE_WARNING(4456) /* declaration hides local */ \ | |
FOLLY_MSVC_DISABLE_WARNING(4457) /* declaration hides parameter */ \ | |
FOLLY_MSVC_DISABLE_WARNING(4458) /* declaration hides member */ \ | |
FOLLY_MSVC_DISABLE_WARNING(4459) /* declaration hides global */ \ | |
FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS \ | |
if (bool SYNCHRONIZED_VAR(state) = false) { \ | |
(void)::folly::detail::SYNCHRONIZED_macro_is_deprecated{}; \ | |
} else \ | |
for (auto SYNCHRONIZED_VAR(lockedPtr) = \ | |
(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).contextualLock(); \ | |
!SYNCHRONIZED_VAR(state); \ | |
SYNCHRONIZED_VAR(state) = true) \ | |
for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \ | |
*SYNCHRONIZED_VAR(lockedPtr).operator->(); \ | |
!SYNCHRONIZED_VAR(state); \ | |
SYNCHRONIZED_VAR(state) = true) \ | |
FOLLY_POP_WARNING | |
/** | |
* NOTE: This API is deprecated. Use lock(), wlock(), rlock() or the withLock | |
* functions instead. In the future it will be marked with a deprecation | |
* attribute to emit build-time warnings, and then it will be removed entirely. | |
* | |
* Similar to SYNCHRONIZED, but only uses a read lock. | |
*/ | |
#define SYNCHRONIZED_CONST(...) \ | |
SYNCHRONIZED( \ | |
FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \ | |
as_const(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__)))) | |
/** | |
* NOTE: This API is deprecated. Use lock(), wlock(), rlock() or the withLock | |
* functions instead. In the future it will be marked with a deprecation | |
* attribute to emit build-time warnings, and then it will be removed entirely. | |
* | |
* Synchronizes two Synchronized objects (they may encapsulate | |
* different data). Synchronization is done in increasing address of | |
* object order, so there is no deadlock risk. | |
*/ | |
#define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \ | |
if (bool SYNCHRONIZED_VAR(state) = false) { \ | |
(void)::folly::detail::SYNCHRONIZED_macro_is_deprecated{}; \ | |
} else \ | |
for (auto SYNCHRONIZED_VAR(ptrs) = acquireLockedPair(e1, e2); \ | |
!SYNCHRONIZED_VAR(state); \ | |
SYNCHRONIZED_VAR(state) = true) \ | |
for (auto& n1 = *SYNCHRONIZED_VAR(ptrs).first; !SYNCHRONIZED_VAR(state); \ | |
SYNCHRONIZED_VAR(state) = true) \ | |
for (auto& n2 = *SYNCHRONIZED_VAR(ptrs).second; \ | |
!SYNCHRONIZED_VAR(state); \ | |
SYNCHRONIZED_VAR(state) = true) | |
} /* namespace folly */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment