Commit b1fa3c6f authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook GitHub Bot

expand the lock protocol and facilities

Summary:
Define a single implementation type `lock_base` which handles all cases, including unique/shared/upgrade locks and including sans-state/with-state locks. Add `unique_lock_base`, `shared_lock_base`, and `upgrade_lock_base`.

Revise `upgrade_lock` simply to derive `upgrade_lock_base`. We may use `upgrade_lock` as an example for specializing `unique_lock` and `shared_lock`

Remove `ProxyLockableUniqueLock` since `unique_lock_base` absorbs it. Let the `unique_lock` specializations for `DistributedMutex` inherit `unique_lock_base` instead.

Add lock invokers for every lock, try-lock, unlock, and lock-transition member. Were these used only internally to implement the lock-policy types and the lock-transition functions they might be left in detail but there may be broader use-cases for at least some of them. Putting the invokers in this header is consistent with proper placement since this header is intended to own all lock primitives and facilities.

Revise the lock-transition functions to handle cases where the from and to lock types are sans-state/with-state. Expand the set of lock-transition functions for completeness: lock-transitions include x->s, x->u, u->s, u->x; while try-lock-transitions include s->x, s->u, u->x.

Reviewed By: aary

Differential Revision: D28767313

fbshipit-source-id: 153adc8270f0f4338db6acf544b8d358556d6f49
parent 4215b920
......@@ -37,7 +37,7 @@
#include <folly/portability/Asm.h>
#include <folly/synchronization/AtomicNotification.h>
#include <folly/synchronization/AtomicUtil.h>
#include <folly/synchronization/DistributedMutex.h>
#include <folly/synchronization/Lock.h>
#include <folly/synchronization/detail/InlineFunctionRef.h>
#include <folly/synchronization/detail/Sleeper.h>
......@@ -1700,3 +1700,31 @@ DistributedMutex<Atomic, TimePublishing>::try_lock_for(
} // namespace distributed_mutex
} // namespace detail
} // namespace folly
namespace std {
template <template <typename> class Atom, bool TimePublishing>
class unique_lock<
::folly::detail::distributed_mutex::DistributedMutex<Atom, TimePublishing>>
: public ::folly::unique_lock_base<
::folly::detail::distributed_mutex::
DistributedMutex<Atom, TimePublishing>> {
public:
using ::folly::unique_lock_base<
::folly::detail::distributed_mutex::
DistributedMutex<Atom, TimePublishing>>::unique_lock_base;
};
template <template <typename> class Atom, bool TimePublishing>
class lock_guard<
::folly::detail::distributed_mutex::DistributedMutex<Atom, TimePublishing>>
: public ::folly::lock_guard_base<
::folly::detail::distributed_mutex::
DistributedMutex<Atom, TimePublishing>> {
public:
using ::folly::lock_guard_base<
::folly::detail::distributed_mutex::
DistributedMutex<Atom, TimePublishing>>::lock_guard_base;
};
} // namespace std
......@@ -340,4 +340,3 @@ using DistributedMutex = detail::distributed_mutex::DistributedMutex<>;
} // namespace folly
#include <folly/synchronization/DistributedMutex-inl.h>
#include <folly/synchronization/DistributedMutexSpecializations.h>
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/synchronization/DistributedMutex.h>
#include <folly/synchronization/detail/ProxyLockable.h>
/**
* Specializations for DistributedMutex allow us to use it like a normal
* mutex. Even though it has a non-usual interface
*/
namespace std {
template <template <typename> class Atom, bool TimePublishing>
class unique_lock<
::folly::detail::distributed_mutex::DistributedMutex<Atom, TimePublishing>>
: public ::folly::detail::ProxyLockableUniqueLock<
::folly::detail::distributed_mutex::
DistributedMutex<Atom, TimePublishing>> {
public:
using ::folly::detail::ProxyLockableUniqueLock<
::folly::detail::distributed_mutex::
DistributedMutex<Atom, TimePublishing>>::ProxyLockableUniqueLock;
};
template <template <typename> class Atom, bool TimePublishing>
class lock_guard<
::folly::detail::distributed_mutex::DistributedMutex<Atom, TimePublishing>>
: public ::folly::detail::ProxyLockableLockGuard<
::folly::detail::distributed_mutex::
DistributedMutex<Atom, TimePublishing>> {
public:
using ::folly::detail::ProxyLockableLockGuard<
::folly::detail::distributed_mutex::
DistributedMutex<Atom, TimePublishing>>::ProxyLockableLockGuard;
};
} // namespace std
......@@ -20,176 +20,680 @@
#include <mutex>
#include <shared_mutex>
#include <system_error>
#include <type_traits>
#include <folly/Portability.h>
#include <folly/Traits.h>
#include <folly/functional/Invoke.h>
#include <folly/lang/Exception.h>
namespace folly {
// upgrade_lock
//
// A lock-holder type which holds upgrade locks, usable with any shared mutex
// type which supports the upgrade state. Similar to std::unique_lock vis-a-vis
// all mutex types or to std::shared_lock vis-a-vis all shared mutex types.
namespace access {
// 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.
//
// As long as the associated mutex type exposes members in the style:
// - lock_upgrade void()
// - try_lock_upgrade bool()
// - try_lock_upgrade_for bool(std::chrono::duration<...> const&)
// - try_lock_upgrade_until bool(std::chrono::time_point<...> const&)
// - unlock_upgrade
// 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.
//
// 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.
// 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 FOLLY_NOINLINE check_fail_() {
auto code = std::errc::operation_not_permitted;
throw_exception<std::system_error>(std::make_error_code(code));
}
};
template <typename Mutex>
class upgrade_lock {
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,
invoke_result_t<typename Policy::lock_fn, Mutex&>> {
public:
using mutex_type = Mutex;
using state_type = 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");
upgrade_lock() noexcept = default;
upgrade_lock(upgrade_lock&& that) noexcept
: mutex_{std::exchange(that.mutex_, nullptr)},
owns_{std::exchange(that.owns_, false)} {}
upgrade_lock(mutex_type& mutex) : mutex_{&mutex}, owns_{true} {
mutex.lock_upgrade();
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();
}
upgrade_lock(mutex_type& mutex, std::defer_lock_t) noexcept
: mutex_{&mutex} {}
upgrade_lock(mutex_type& mutex, std::try_to_lock_t)
: mutex_{&mutex}, owns_{mutex.try_lock_upgrade()} {}
upgrade_lock(mutex_type& mutex, std::adopt_lock_t)
: mutex_{&mutex}, owns_{true} {}
template <typename Rep, typename Period>
upgrade_lock(
mutex_type& mutex, std::chrono::duration<Rep, Period> const& timeout)
: mutex_{&mutex}, owns_{mutex.try_lock_upgrade_for(timeout)} {}
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>
upgrade_lock(
lock_base(
mutex_type& mutex,
std::chrono::time_point<Clock, Duration> const& deadline)
: mutex_{&mutex}, owns_{mutex.try_lock_upgrade_until(deadline)} {}
std::chrono::time_point<Clock, Duration> const& deadline) {
storage::mutex_ = std::addressof(mutex);
try_lock_until(deadline);
}
~upgrade_lock() {
if (owns_) {
mutex_->unlock_upgrade();
~lock_base() {
if (owns_lock()) {
unlock();
}
}
upgrade_lock& operator=(upgrade_lock&& that) noexcept {
lock_base& operator=(lock_base&& that) noexcept {
if (owns_lock()) {
unlock();
}
mutex_ = std::exchange(that.mutex_, nullptr);
owns_ = std::exchange(that.owns_, false);
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>();
mutex_->lock_upgrade();
owns_ = true;
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>();
return owns_ = mutex_->try_lock_upgrade();
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>();
return owns_ = mutex_->try_lock_upgrade_for(timeout);
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>();
return owns_ = mutex_->try_lock_upgrade_until(deadline);
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>();
mutex_->unlock_upgrade();
owns_ = false;
auto const& state = storage::state_; // prevent unlock from mutating state_
typename Policy::unlock_fn{}(*storage::mutex_, state);
storage::state_ = decltype(storage::state_){};
}
void swap(upgrade_lock& that) noexcept {
std::swap(mutex_, that.mutex_);
std::swap(owns_, that.owns_);
mutex_type* release() noexcept {
storage::state_ = {};
return std::exchange(storage::mutex_, nullptr);
}
friend void swap(upgrade_lock& a, upgrade_lock& b) noexcept { a.swap(b); }
mutex_type* mutex() const noexcept { return storage::mutex_; }
mutex_type* release() noexcept {
owns_ = false;
return std::exchange(mutex_, nullptr);
template <bool C = has_state_, if_<C> = 0>
state_type state() const noexcept {
return storage::state_;
}
mutex_type* mutex() const noexcept { return mutex_; }
bool owns_lock() const noexcept { return !!storage::state_; }
bool owns_lock() const noexcept { return owns_; }
explicit operator bool() const noexcept { return !!storage::state_; }
explicit operator bool() const noexcept { return owns_; }
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 (!mutex_ || owns_ != Owns) {
check_<Owns>();
if (!storage::mutex_ || !storage::state_ == Owns) {
check_fail_<Owns>();
}
}
template <bool Owns>
[[noreturn]] FOLLY_NOINLINE void check_() {
throw_exception<std::system_error>(std::make_error_code(
!mutex_ || !owns_ ? std::errc::operation_not_permitted
: std::errc::resource_deadlock_would_occur));
[[noreturn]] FOLLY_NOINLINE 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_exception<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_ = 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;
};
mutex_type* mutex_{nullptr};
bool owns_{false};
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_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>;
} // 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
//
// 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); }
};
// 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;
};
// 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 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;
};
// lock_guard
//
// Alias to std::lock_guard.
using std::lock_guard;
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 = invoke_result_t<Transition, Mutex&, State const&, A const&...>;
};
template <>
struct transition_lock_result_<void> {
template <typename Transition, typename Mutex, typename... A>
using apply = 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) {
return transition(*lock.mutex(), 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) {
return transition(*lock.mutex(), lock.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 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 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 s = transition_lock_1_(lock, transition, a...);
return !s ? To{} : To{*lock.release(), 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 s = transition_lock_1_(lock, transition, a...);
return !s ? To{} : To{*lock.release(), std::adopt_lock, s};
}
template <
template <typename>
class To,
template <typename>
class From,
typename Mutex,
typename Transition>
To<Mutex> try_transition_lock_(From<Mutex>& lock, Transition transition) {
auto owns = lock.owns_lock();
if (!lock.mutex()) {
return To<Mutex>{};
}
if (!owns) {
return To<Mutex>{*lock.release(), std::defer_lock};
}
if (!transition(*lock.mutex())) {
return To<Mutex>{};
}
return To<Mutex>{*lock.release(), std::adopt_lock};
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} :
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 To,
class ToLock,
typename Mutex,
template <typename>
class From,
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,
typename Transition>
To<Mutex> transition_lock_(From<Mutex>& lock, Transition transition) {
return try_transition_lock_<To>(lock, [&](auto& l) { //
return transition(l), true;
});
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, _);
}
} // namespace detail
// 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)
//
......@@ -198,11 +702,8 @@ To<Mutex> transition_lock_(From<Mutex>& lock, Transition transition) {
// Represents an immediate atomic downgrade transition from exclusive lock to
// to shared lock.
template <typename Mutex>
std::shared_lock<Mutex> transition_to_shared_lock(
std::unique_lock<Mutex>& lock) {
return detail::transition_lock_<std::shared_lock>(lock, [](auto& _) { //
_.unlock_and_lock_shared();
});
shared_lock<Mutex> transition_to_shared_lock(unique_lock<Mutex>& lock) {
return transition_lock<shared_lock>(lock);
}
// transition_to_shared_lock(upgrade_lock)
......@@ -212,11 +713,8 @@ std::shared_lock<Mutex> transition_to_shared_lock(
// Represents an immediate atomic downgrade transition from upgrade lock to
// shared lock.
template <typename Mutex>
std::shared_lock<Mutex> transition_to_shared_lock( //
upgrade_lock<Mutex>& lock) {
return detail::transition_lock_<std::shared_lock>(lock, [](auto& _) { //
_.unlock_upgrade_and_lock_shared();
});
shared_lock<Mutex> transition_to_shared_lock(upgrade_lock<Mutex>& lock) {
return transition_lock<shared_lock>(lock);
}
// transition_to_upgrade_lock(unique_lock)
......@@ -226,11 +724,8 @@ std::shared_lock<Mutex> transition_to_shared_lock( //
// Represents an immediate atomic downgrade transition from unique lock to
// upgrade lock.
template <typename Mutex>
upgrade_lock<Mutex> transition_to_upgrade_lock( //
std::unique_lock<Mutex>& lock) {
return detail::transition_lock_<upgrade_lock>(lock, [](auto& _) { //
_.unlock_and_lock_upgrade();
});
upgrade_lock<Mutex> transition_to_upgrade_lock(unique_lock<Mutex>& lock) {
return transition_lock<upgrade_lock>(lock);
}
// transition_to_unique_lock(upgrade_lock)
......@@ -240,11 +735,8 @@ upgrade_lock<Mutex> transition_to_upgrade_lock( //
// Represents an eventual atomic upgrade transition from upgrade lock to unique
// lock.
template <typename Mutex>
std::unique_lock<Mutex> transition_to_unique_lock( //
upgrade_lock<Mutex>& lock) {
return detail::transition_lock_<std::unique_lock>(lock, [](auto& _) { //
_.unlock_upgrade_and_lock();
});
unique_lock<Mutex> transition_to_unique_lock(upgrade_lock<Mutex>& lock) {
return transition_lock<unique_lock>(lock);
}
// try_transition_to_unique_lock(upgrade_lock)
......@@ -254,11 +746,8 @@ std::unique_lock<Mutex> transition_to_unique_lock( //
// Represents an immediate attempted atomic upgrade transition from upgrade
// lock to unique lock.
template <typename Mutex>
std::unique_lock<Mutex> try_transition_to_unique_lock(
upgrade_lock<Mutex>& lock) {
return detail::try_transition_lock_<std::unique_lock>(lock, [](auto& _) { //
return _.try_unlock_upgrade_and_lock();
});
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)
......@@ -268,27 +757,97 @@ std::unique_lock<Mutex> try_transition_to_unique_lock(
// Represents an eventual attempted atomic upgrade transition from upgrade
// lock to unique lock.
template <typename Mutex, typename Rep, typename Period>
std::unique_lock<Mutex> try_transition_to_unique_lock_for(
unique_lock<Mutex> try_transition_to_unique_lock_for(
upgrade_lock<Mutex>& lock,
std::chrono::duration<Rep, Period> const& timeout) {
return detail::try_transition_lock_<std::unique_lock>(lock, [&](auto& _) {
return _.try_unlock_upgrade_and_lock_for(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 attemped atomic upgrade transition from upgrade
// Represents an eventual attempted atomic upgrade transition from upgrade
// lock to unique lock.
template <typename Mutex, typename Clock, typename Duration>
std::unique_lock<Mutex> try_transition_to_unique_lock_until(
unique_lock<Mutex> try_transition_to_unique_lock_until(
upgrade_lock<Mutex>& lock,
std::chrono::time_point<Clock, Duration> const& deadline) {
return detail::try_transition_lock_<std::unique_lock>(lock, [&](auto& _) {
return _.try_unlock_upgrade_and_lock_until(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
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <cassert>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <utility>
#include <folly/Optional.h>
#include <folly/Portability.h>
namespace folly {
namespace detail {
namespace proxylockable_detail {
template <typename Bool>
void throwIfAlreadyLocked(Bool&& locked) {
if (kIsDebug && locked) {
throw std::system_error{
std::make_error_code(std::errc::resource_deadlock_would_occur)};
}
}
template <typename Bool>
void throwIfNotLocked(Bool&& locked) {
if (kIsDebug && !locked) {
throw std::system_error{
std::make_error_code(std::errc::operation_not_permitted)};
}
}
template <typename Bool>
void throwIfNoMutex(Bool&& mutex) {
if (kIsDebug && !mutex) {
throw std::system_error{
std::make_error_code(std::errc::operation_not_permitted)};
}
}
} // namespace proxylockable_detail
template <typename Mutex>
ProxyLockableUniqueLock<Mutex>::~ProxyLockableUniqueLock() {
if (owns_lock()) {
unlock();
}
}
template <typename Mutex>
ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(mutex_type& mutex)
: mutex_{std::addressof(mutex)}, state_{mutex.lock()} {}
template <typename Mutex>
ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
ProxyLockableUniqueLock&& a) noexcept
: mutex_{std::exchange(a.mutex_, nullptr)},
state_{std::exchange(a.state_, state_type{})} {}
template <typename Mutex>
ProxyLockableUniqueLock<Mutex>& ProxyLockableUniqueLock<Mutex>::operator=(
ProxyLockableUniqueLock&& other) noexcept {
if (owns_lock()) {
unlock();
}
mutex_ = std::exchange(other.mutex_, nullptr);
state_ = std::exchange(other.state_, state_type{});
return *this;
}
template <typename Mutex>
ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
mutex_type& mutex, std::adopt_lock_t, const state_type& state)
: mutex_{std::addressof(mutex)}, state_{state} {
proxylockable_detail::throwIfNotLocked(state_);
}
template <typename Mutex>
ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
mutex_type& mutex, std::defer_lock_t) noexcept
: mutex_{std::addressof(mutex)} {}
template <typename Mutex>
ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
mutex_type& mutex, std::try_to_lock_t)
: mutex_{std::addressof(mutex)}, state_{mutex.try_lock()} {}
template <typename Mutex>
template <typename Rep, typename Period>
ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
mutex_type& mutex, const std::chrono::duration<Rep, Period>& timeout)
: mutex_{std::addressof(mutex)}, state_{mutex.try_lock_for(timeout)} {}
template <typename Mutex>
template <typename Clock, typename Duration>
ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
mutex_type& mutex, const std::chrono::time_point<Clock, Duration>& deadline)
: mutex_{std::addressof(mutex)}, state_{mutex.try_lock_until(deadline)} {}
template <typename Mutex>
void ProxyLockableUniqueLock<Mutex>::lock() {
proxylockable_detail::throwIfAlreadyLocked(state_);
proxylockable_detail::throwIfNoMutex(mutex_);
state_ = mutex_->lock();
}
template <typename Mutex>
void ProxyLockableUniqueLock<Mutex>::unlock() {
proxylockable_detail::throwIfNoMutex(mutex_);
proxylockable_detail::throwIfNotLocked(state_);
const auto& state = state_;
mutex_->unlock(state);
state_ = state_type{};
}
template <typename Mutex>
bool ProxyLockableUniqueLock<Mutex>::try_lock() {
proxylockable_detail::throwIfNoMutex(mutex_);
proxylockable_detail::throwIfAlreadyLocked(state_);
state_ = mutex_->try_lock();
return !!state_;
}
template <typename Mutex>
template <typename Rep, typename Period>
bool ProxyLockableUniqueLock<Mutex>::try_lock_for(
const std::chrono::duration<Rep, Period>& timeout) {
proxylockable_detail::throwIfNoMutex(mutex_);
proxylockable_detail::throwIfAlreadyLocked(state_);
state_ = mutex_->try_lock_for(timeout);
return !!state_;
}
template <typename Mutex>
template <typename Clock, typename Duration>
bool ProxyLockableUniqueLock<Mutex>::try_lock_until(
const std::chrono::time_point<Clock, Duration>& deadline) {
proxylockable_detail::throwIfNoMutex(mutex_);
proxylockable_detail::throwIfAlreadyLocked(state_);
state_ = mutex_->try_lock_until(deadline);
return !!state_;
}
template <typename Mutex>
void ProxyLockableUniqueLock<Mutex>::swap(
ProxyLockableUniqueLock& other) noexcept {
std::swap(mutex_, other.mutex_);
std::swap(state_, other.state_);
}
template <typename Mutex>
typename ProxyLockableUniqueLock<Mutex>::mutex_type*
ProxyLockableUniqueLock<Mutex>::mutex() const noexcept {
return mutex_;
}
template <typename Mutex>
typename ProxyLockableUniqueLock<Mutex>::state_type const&
ProxyLockableUniqueLock<Mutex>::state() const noexcept {
return state_;
}
template <typename Mutex>
typename ProxyLockableUniqueLock<Mutex>::state_type&
ProxyLockableUniqueLock<Mutex>::state() noexcept {
return state_;
}
template <typename Mutex>
bool ProxyLockableUniqueLock<Mutex>::owns_lock() const noexcept {
return !!state_;
}
template <typename Mutex>
ProxyLockableUniqueLock<Mutex>::operator bool() const noexcept {
return owns_lock();
}
template <typename Mutex>
ProxyLockableLockGuard<Mutex>::ProxyLockableLockGuard(mutex_type& mutex)
: ProxyLockableUniqueLock<Mutex>{mutex} {}
} // namespace detail
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <mutex>
#include <folly/Optional.h>
namespace folly {
namespace detail {
/**
* ProxyLockable is a "concept" that is used usually for mutexes that don't
* return void, but rather a state object that contains data that should be
* passed to the unlock function.
*
* This is in contrast with the normal Lockable concept that imposes no
* requirement on the return type of lock(), and requires an unlock() with no
* parameters. Here we require that lock() returns non-void and that unlock()
* accepts the return type of lock() by value, rvalue-reference or
* const-reference
*
* Here we define two classes, that can be used by the top level to implement
* specializations for std::unique_lock and std::lock_guard. Both
* ProxyLockableUniqueLock and ProxyLockableLockGuard implement the entire
* interface of std::unique_lock and std::lock_guard respectively
*/
template <typename Mutex>
class ProxyLockableUniqueLock {
public:
using mutex_type = Mutex;
using state_type = std::decay_t<decltype(std::declval<mutex_type&>().lock())>;
/**
* Default constructor initializes the unique_lock to an empty state
*/
ProxyLockableUniqueLock() = default;
/**
* Destructor releases the mutex if it is locked
*/
~ProxyLockableUniqueLock();
/**
* Move constructor and move assignment operators take state from the other
* lock
*/
ProxyLockableUniqueLock(ProxyLockableUniqueLock&& other) noexcept;
ProxyLockableUniqueLock& operator=(ProxyLockableUniqueLock&&) noexcept;
/**
* Locks the mutex, blocks until the mutex can be acquired.
*
* The mutex is guaranteed to be acquired after this function returns.
*/
explicit ProxyLockableUniqueLock(mutex_type&);
/**
* Explicit locking constructors to control how the lock() method is called
*
* std::adopt_lock_t causes the mutex to get tracked, with provided lock state
* std::defer_lock_t causes the mutex to get tracked, but not locked
* std::try_to_lock_t causes try_lock() to be called. The current object is
* converts to true if the lock was successful
*/
ProxyLockableUniqueLock(
mutex_type& mutex, std::adopt_lock_t, const state_type& state);
ProxyLockableUniqueLock(mutex_type& mutex, std::defer_lock_t) noexcept;
ProxyLockableUniqueLock(mutex_type& mutex, std::try_to_lock_t);
/**
* Timed locking constructors
*/
template <typename Rep, typename Period>
ProxyLockableUniqueLock(
mutex_type& mutex, const std::chrono::duration<Rep, Period>& duration);
template <typename Clock, typename Duration>
ProxyLockableUniqueLock(
mutex_type& mutex, const std::chrono::time_point<Clock, Duration>& time);
/**
* Lock and unlock methods
*
* lock() and try_lock() throw if the mutex is already locked, or there is
* no mutex. unlock() throws if there is no mutex or if the mutex was not
* locked
*/
void lock();
void unlock();
bool try_lock();
/**
* Timed locking methods
*
* These throw if there was no mutex, or if the mutex was already locked
*/
template <typename Rep, typename Period>
bool try_lock_for(const std::chrono::duration<Rep, Period>& timeout);
template <typename Clock, typename Duration>
bool try_lock_until(const std::chrono::time_point<Clock, Duration>& deadline);
/**
* Swap this unique lock with the other one
*/
void swap(ProxyLockableUniqueLock& other) noexcept;
/**
* Returns true if the unique lock contains a lock and also has acquired an
* exclusive lock successfully
*/
bool owns_lock() const noexcept;
explicit operator bool() const noexcept;
/**
* mutex() return a pointer to the mutex if there is a contained mutex and
* state() returns a pointer to the contained state if the mutex is locked
*
* If the unique lock was not constructed with a mutex, then mutex() returns
* nullptr. If the mutex is not locked, then state() returns nullptr
*/
mutex_type* mutex() const noexcept;
state_type const& state() const noexcept;
state_type& state() noexcept;
private:
friend class ProxyLockableTest;
/**
* If the optional has a value, the mutex is locked, if it is empty, it is
* not
*/
mutex_type* mutex_{nullptr};
state_type state_{};
};
template <typename Mutex>
class ProxyLockableLockGuard : private ProxyLockableUniqueLock<Mutex> {
public:
using mutex_type = Mutex;
/**
* Constructor locks the mutex, and destructor unlocks
*/
ProxyLockableLockGuard(mutex_type& mutex);
~ProxyLockableLockGuard() = default;
/**
* This class is not movable or assignable
*
* For more complicated usecases, consider the UniqueLock variant, which
* provides more options
*/
ProxyLockableLockGuard(const ProxyLockableLockGuard&) = delete;
ProxyLockableLockGuard(ProxyLockableLockGuard&&) = delete;
ProxyLockableLockGuard& operator=(ProxyLockableLockGuard&&) = delete;
ProxyLockableLockGuard& operator=(const ProxyLockableLockGuard&) = delete;
};
} // namespace detail
} // namespace folly
#include <folly/synchronization/detail/ProxyLockable-inl.h>
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/synchronization/detail/ProxyLockable.h>
#include <mutex>
#include <tuple>
#include <folly/Benchmark.h>
namespace folly {
namespace detail {
namespace {
class StdMutexWrapper {
public:
int lock() {
mutex_.lock();
return 1;
}
void unlock(int) { mutex_.unlock(); }
std::mutex mutex_{};
};
} // namespace
BENCHMARK(StdMutexWithoutUniqueLock, iters) {
auto&& mutex = std::mutex{};
for (auto i = std::size_t{0}; i < iters; ++i) {
mutex.lock();
mutex.unlock();
}
}
BENCHMARK(StdMutexWithUniqueLock, iters) {
auto&& mutex = std::mutex{};
for (auto i = std::size_t{0}; i < iters; ++i) {
auto&& lck = std::unique_lock<std::mutex>{mutex};
std::ignore = lck;
}
}
BENCHMARK(StdMutexWithLockGuard, iters) {
auto&& mutex = std::mutex{};
for (auto i = std::size_t{0}; i < iters; ++i) {
auto&& lck = std::lock_guard<std::mutex>{mutex};
std::ignore = lck;
}
}
BENCHMARK(StdMutexWithProxyLockableUniqueLock, iters) {
auto&& mutex = StdMutexWrapper{};
for (auto i = std::size_t{0}; i < iters; ++i) {
auto&& lck = ProxyLockableUniqueLock<StdMutexWrapper>{mutex};
std::ignore = lck;
}
}
BENCHMARK(StdMutexWithProxyLockableLockGuard, iters) {
auto&& mutex = StdMutexWrapper{};
for (auto i = std::size_t{0}; i < iters; ++i) {
auto&& lck = ProxyLockableLockGuard<StdMutexWrapper>{mutex};
std::ignore = lck;
}
}
} // namespace detail
} // namespace folly
int main(int argc, char** argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
folly::runBenchmarks();
}
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/synchronization/detail/ProxyLockable.h>
#include <atomic>
#include <chrono>
#include <mutex>
#include <thread>
#include <tuple>
#include <vector>
#include <folly/Benchmark.h>
#include <folly/Random.h>
#include <folly/portability/GTest.h>
#include <folly/synchronization/DistributedMutex.h>
using namespace std::literals;
namespace folly {
namespace detail {
namespace {
DEFINE_int64(stress_test_seconds, 2, "Duration for stress tests");
class MockMutex {
public:
int lock() {
++locked_;
return 1;
}
void unlock(int integer) {
--locked_;
EXPECT_EQ(integer, 1);
}
int try_lock() {
if (!locked_) {
return lock();
}
return 0;
}
template <typename Duration>
int try_lock_for(const Duration&) {
return try_lock();
}
template <typename TimePoint>
int try_lock_until(const TimePoint&) {
return try_lock();
}
// counts the number of times the mutex has been locked
int locked_{0};
};
} // namespace
class ProxyLockableTest : public ::testing::Test {};
TEST_F(ProxyLockableTest, UniqueLockBasic) {
auto mutex = MockMutex{};
auto lck = ProxyLockableUniqueLock<MockMutex>{mutex};
std::ignore = lck;
EXPECT_EQ(mutex.locked_, 1);
}
TEST_F(ProxyLockableTest, UniqueLockDefaultConstruct) {
auto lck = ProxyLockableUniqueLock<MockMutex>{};
EXPECT_FALSE(lck.mutex());
EXPECT_FALSE(lck.state());
EXPECT_FALSE(lck.owns_lock());
EXPECT_FALSE(lck.operator bool());
}
TEST_F(ProxyLockableTest, UniqueLockLockOnConstruct) {
auto mutex = MockMutex{};
auto lck = ProxyLockableUniqueLock<MockMutex>{mutex};
EXPECT_TRUE(lck.mutex());
EXPECT_TRUE(lck.state());
EXPECT_EQ(mutex.locked_, 1);
}
TEST_F(ProxyLockableTest, UniqueLockConstructMoveConstructAssign) {
auto mutex = MockMutex{};
auto one = ProxyLockableUniqueLock<MockMutex>{mutex};
EXPECT_TRUE(one.mutex());
EXPECT_TRUE(one.state());
auto two = std::move(one);
EXPECT_FALSE(one.mutex());
EXPECT_FALSE(one.state());
EXPECT_FALSE(one.owns_lock());
EXPECT_FALSE(one.operator bool());
EXPECT_TRUE(two.mutex());
EXPECT_TRUE(two.state());
auto three = std::move(one);
EXPECT_FALSE(one.mutex());
EXPECT_FALSE(one.mutex());
EXPECT_FALSE(three.mutex());
EXPECT_FALSE(three.mutex());
auto four = std::move(two);
EXPECT_TRUE(four.mutex());
EXPECT_TRUE(four.state());
EXPECT_FALSE(one.state());
EXPECT_FALSE(one.state());
EXPECT_EQ(mutex.locked_, 1);
four = std::move(three);
EXPECT_EQ(mutex.locked_, 0);
EXPECT_FALSE(four.mutex());
EXPECT_FALSE(four.state());
four = ProxyLockableUniqueLock<MockMutex>{mutex};
EXPECT_EQ(mutex.locked_, 1);
EXPECT_TRUE(four.mutex());
EXPECT_TRUE(four.state());
four = ProxyLockableUniqueLock<MockMutex>{};
EXPECT_EQ(mutex.locked_, 0);
EXPECT_FALSE(four.mutex());
EXPECT_FALSE(four.state());
}
TEST_F(ProxyLockableTest, UniqueLockAdoptLock) {
auto mutex = MockMutex{};
auto state = mutex.lock();
auto lck = ProxyLockableUniqueLock<MockMutex>{mutex, std::adopt_lock, state};
EXPECT_EQ(mutex.locked_, 1);
lck.unlock();
EXPECT_EQ(mutex.locked_, 0);
}
TEST_F(ProxyLockableTest, UniqueLockDeferLock) {
auto mutex = MockMutex{};
auto lck = ProxyLockableUniqueLock<MockMutex>{mutex, std::defer_lock};
EXPECT_EQ(mutex.locked_, 0);
lck.lock();
EXPECT_EQ(mutex.locked_, 1);
}
namespace {
template <typename Make>
void testTryToLock(Make make) {
auto mutex = MockMutex{};
{
auto lck = make(mutex);
EXPECT_TRUE(lck.mutex());
EXPECT_TRUE(lck.state());
EXPECT_EQ(mutex.locked_, 1);
}
EXPECT_EQ(mutex.locked_, 0);
mutex.lock();
auto lck = make(mutex);
EXPECT_EQ(mutex.locked_, 1);
EXPECT_TRUE(lck.mutex());
EXPECT_FALSE(lck.state());
}
} // namespace
TEST_F(ProxyLockableTest, UniqueLockTryToLock) {
testTryToLock([](auto& mutex) {
using Mutex = std::decay_t<decltype(mutex)>;
return ProxyLockableUniqueLock<Mutex>{mutex, std::try_to_lock};
});
}
TEST_F(ProxyLockableTest, UniqueLockTimedLockDuration) {
testTryToLock([](auto& mutex) {
using Mutex = std::decay_t<decltype(mutex)>;
return ProxyLockableUniqueLock<Mutex>{mutex, 1s};
});
}
TEST_F(ProxyLockableTest, UniqueLockTimedLockWithTime) {
testTryToLock([](auto& mutex) {
using Mutex = std::decay_t<decltype(mutex)>;
return ProxyLockableUniqueLock<Mutex>{
mutex, std::chrono::steady_clock::now() + 1s};
});
}
TEST_F(ProxyLockableTest, UniqueLockLockExplicitLockAfterDefer) {
auto mutex = MockMutex{};
auto lck = ProxyLockableUniqueLock<MockMutex>{mutex, std::defer_lock};
EXPECT_TRUE(lck.mutex());
EXPECT_FALSE(lck.state());
lck.lock();
EXPECT_TRUE(lck.mutex());
EXPECT_TRUE(lck.state());
EXPECT_EQ(mutex.locked_, 1);
}
TEST_F(ProxyLockableTest, UniqueLockLockExplicitUnlockAfterDefer) {
auto mutex = MockMutex{};
auto lck = ProxyLockableUniqueLock<MockMutex>{mutex, std::defer_lock};
EXPECT_TRUE(lck.mutex());
EXPECT_FALSE(lck.state());
lck.lock();
EXPECT_TRUE(lck.mutex());
EXPECT_TRUE(lck.state());
EXPECT_EQ(mutex.locked_, 1);
lck.unlock();
EXPECT_EQ(mutex.locked_, 0);
}
TEST_F(ProxyLockableTest, UniqueLockLockExplicitTryLockAfterDefer) {
auto mutex = MockMutex{};
auto lck = ProxyLockableUniqueLock<MockMutex>{mutex, std::defer_lock};
EXPECT_TRUE(lck.mutex());
EXPECT_FALSE(lck.state());
EXPECT_TRUE(lck.try_lock());
EXPECT_TRUE(lck.mutex());
EXPECT_TRUE(lck.state());
EXPECT_EQ(mutex.locked_, 1);
lck.unlock();
EXPECT_EQ(mutex.locked_, 0);
}
TEST_F(ProxyLockableTest, UniqueLockExceptionOnLock) {
{
auto lck = ProxyLockableUniqueLock<MockMutex>{};
if (kIsDebug) {
EXPECT_THROW(lck.lock(), std::system_error);
}
}
{
auto mutex = MockMutex{};
auto lck = ProxyLockableUniqueLock<MockMutex>{mutex};
if (kIsDebug) {
EXPECT_THROW(lck.lock(), std::system_error);
}
}
}
TEST_F(ProxyLockableTest, UniqueLockExceptionOnUnlock) {
{
auto lck = ProxyLockableUniqueLock<MockMutex>{};
if (kIsDebug) {
EXPECT_THROW(lck.unlock(), std::system_error);
}
}
{
auto mutex = MockMutex{};
auto lck = ProxyLockableUniqueLock<MockMutex>{mutex};
lck.unlock();
if (kIsDebug) {
EXPECT_THROW(lck.unlock(), std::system_error);
}
}
}
TEST_F(ProxyLockableTest, UniqueLockExceptionOnTryLock) {
{
auto lck = ProxyLockableUniqueLock<MockMutex>{};
if (kIsDebug) {
EXPECT_THROW(lck.try_lock(), std::system_error);
}
}
{
auto mutex = MockMutex{};
auto lck = ProxyLockableUniqueLock<MockMutex>{mutex};
if (kIsDebug) {
EXPECT_THROW(lck.try_lock(), std::system_error);
}
}
}
namespace {
class StdMutexWrapper {
public:
int lock() {
mutex_.lock();
return 1;
}
void unlock(int value) {
EXPECT_EQ(value, 1);
mutex_.unlock();
}
std::mutex mutex_{};
};
template <typename Mutex>
void stressTest() {
const auto&& kNumThreads = std::thread::hardware_concurrency();
auto&& mutex = Mutex{};
auto&& threads = std::vector<std::thread>{};
auto&& atomic = std::atomic<std::uint64_t>{0};
auto&& stop = std::atomic<bool>{false};
// try and randomize thread scheduling
auto&& randomize = [] {
if (folly::Random::oneIn(100)) {
/* sleep override */
std::this_thread::sleep_for(500us);
}
};
for (auto i = std::size_t{0}; i < kNumThreads; ++i) {
threads.emplace_back([&] {
while (!stop.load()) {
auto lck = ProxyLockableUniqueLock<Mutex>{mutex};
EXPECT_EQ(atomic.fetch_add(1, std::memory_order_relaxed), 0);
randomize();
EXPECT_EQ(atomic.fetch_sub(1, std::memory_order_relaxed), 1);
}
});
}
/* sleep override */
std::this_thread::sleep_for(std::chrono::seconds{FLAGS_stress_test_seconds});
stop.store(true);
for (auto& thread : threads) {
thread.join();
}
}
} // namespace
TEST_F(ProxyLockableTest, StressLockOnConstructionStdMutex) {
stressTest<StdMutexWrapper>();
}
TEST_F(ProxyLockableTest, StressLockOnConstructionFollyDistributedMutex) {
stressTest<folly::DistributedMutex>();
}
TEST_F(ProxyLockableTest, LockGuardBasic) {
auto mutex = MockMutex{};
auto&& lck = ProxyLockableLockGuard<MockMutex>{mutex};
std::ignore = lck;
EXPECT_TRUE(mutex.locked_);
}
} // namespace detail
} // namespace folly
......@@ -16,14 +16,19 @@
#include <folly/synchronization/Lock.h>
#include <functional>
#include <tuple>
#include <folly/portability/GTest.h>
using namespace std::literals::chrono_literals;
class LockTest : public testing::Test {};
namespace {
// a fake mutex type and associated types for use in testing
namespace q = folly::access;
using Clock = std::chrono::steady_clock;
class UnownedError : public std::runtime_error {
......@@ -41,73 +46,192 @@ class MismatchError : public std::runtime_error {
MismatchError() : std::runtime_error::runtime_error("mismatch") {}
};
struct Mutex {
enum class Held { None, Unique, Shared, Upgrade };
enum class Held { None, Unique, Shared, Upgrade };
template <
typename UniqueLockState,
typename SharedLockState,
typename UpgradeLockState>
class Mutex {
private:
// template magic helpers:
// - ensure that lock functions return void or state
// - ensure that try-lock functions return bool or state
// - ensure that unlock functions take nothing or state
// - ensure that unlock-and-lock functions return void or state and take
// nothing or state
// - ensure that try-unlock-and-lock functions return bool or state and take
// nothing or state
template <bool C>
using if_ = std::enable_if_t<C, int>;
template <typename State>
using v = State;
template <typename State>
using b = std::conditional_t<std::is_void_v<State>, bool, State>;
template <typename State>
struct a_ {
template <typename... A>
static inline constexpr bool apply =
sizeof...(A) == 1 && std::is_constructible_v<State const&, A&&...>;
};
template <>
struct a_<void> {
template <typename... A>
static inline constexpr bool apply = sizeof...(A) == 0;
};
template <typename State, typename... A>
static inline constexpr bool a = a_<State>::template apply<A...>;
template <typename... S>
struct m_ {
using self = m_<S...>;
void operator()(S const&...);
template <typename... A>
static constexpr inline bool apply = std::is_invocable_v<self&, A...>;
};
template <typename... S>
struct m_<void, S...> {
using self = m_<void, S...>;
void operator()(S const&...);
template <typename... A>
static constexpr inline bool apply = std::is_invocable_v<self&, A...>;
};
template <typename... S>
using md_ = m_<S..., Clock::duration>;
template <typename... S>
using mt_ = m_<S..., Clock::time_point>;
template <typename M, typename... A>
static inline constexpr bool ap_ = M::template apply<A const&...>;
public:
Held held = Held::None;
Clock::time_point now = Clock::now();
Clock::time_point locked_until = Clock::time_point::min();
void lock() { op(Held::Unique); }
bool try_lock() { return try_op(Held::Unique); }
bool try_lock_for(Clock::duration timeout) {
return try_op_for(Held::Unique, timeout);
v<UniqueLockState> lock() { //
return op(Held::Unique), v<UniqueLockState>(1);
}
b<UniqueLockState> try_lock() {
return b<UniqueLockState>(try_op(Held::Unique));
}
b<UniqueLockState> try_lock_for(Clock::duration timeout) {
return b<UniqueLockState>(try_op_for(Held::Unique, timeout));
}
b<UniqueLockState> try_lock_until(Clock::time_point deadline) {
return b<UniqueLockState>(try_op_until(Held::Unique, deadline));
}
template <typename... A, if_<a<UniqueLockState, A...>> = 0>
void unlock(A&&... state) {
unop(Held::Unique, state...);
}
v<SharedLockState> lock_shared() {
return op(Held::Shared), v<SharedLockState>(1);
}
b<SharedLockState> try_lock_shared() {
return b<SharedLockState>(try_op(Held::Shared));
}
bool try_lock_until(Clock::time_point deadline) {
return try_op_until(Held::Unique, deadline);
b<SharedLockState> try_lock_shared_for(Clock::duration timeout) {
return b<SharedLockState>(try_op_for(Held::Shared, timeout));
}
b<SharedLockState> try_lock_shared_until(Clock::time_point deadline) {
return b<SharedLockState>(try_op_until(Held::Shared, deadline));
}
template <typename... A, if_<a<SharedLockState, A...>> = 0>
void unlock_shared(A&&... state) {
unop(Held::Shared, state...);
}
void unlock() { unop(Held::Unique); }
void lock_shared() { op(Held::Shared); }
bool try_lock_shared() { return try_op(Held::Shared); }
bool try_lock_shared_for(Clock::duration timeout) {
return try_op_for(Held::Shared, timeout);
v<UpgradeLockState> lock_upgrade() {
return op(Held::Upgrade), v<UpgradeLockState>(1);
}
b<UpgradeLockState> try_lock_upgrade() {
return b<UpgradeLockState>(try_op(Held::Upgrade));
}
bool try_lock_shared_until(Clock::time_point deadline) {
return try_op_until(Held::Shared, deadline);
b<UpgradeLockState> try_lock_upgrade_for(Clock::duration timeout) {
return b<UpgradeLockState>(try_op_for(Held::Upgrade, timeout));
}
b<UpgradeLockState> try_lock_upgrade_until(Clock::time_point deadline) {
return b<UpgradeLockState>(try_op_until(Held::Upgrade, deadline));
}
template <typename... A, if_<a<UpgradeLockState, A...>> = 0>
void unlock_upgrade(A&&... state) {
unop(Held::Upgrade, state...);
}
void unlock_shared() { unop(Held::Shared); }
void lock_upgrade() { op(Held::Upgrade); }
bool try_lock_upgrade() { return try_op(Held::Upgrade); }
bool try_lock_upgrade_for(Clock::duration timeout) {
return try_op_for(Held::Upgrade, timeout);
template <typename... A, if_<a<UniqueLockState, A...>> = 0>
v<SharedLockState> unlock_and_lock_shared(A&&... state) {
return transition_0_(q::unlock, q::lock_shared, state...);
}
template <typename... A, if_<a<UniqueLockState, A...>> = 0>
v<UpgradeLockState> unlock_and_lock_upgrade(A&&... state) {
return transition_0_(q::unlock, q::lock_upgrade, state...);
}
bool try_lock_upgrade_until(Clock::time_point deadline) {
return try_op_until(Held::Upgrade, deadline);
template <typename... A, if_<a<UpgradeLockState, A...>> = 0>
v<UniqueLockState> unlock_upgrade_and_lock(A&&... state) {
return transition_0_(q::unlock_upgrade, q::lock, state...);
}
template <typename... A, if_<a<UpgradeLockState, A...>> = 0>
v<SharedLockState> unlock_upgrade_and_lock_shared(A&&... state) {
return transition_0_(q::unlock_upgrade, q::lock_shared, state...);
}
void unlock_upgrade() { unop(Held::Upgrade); }
void unlock_and_lock_shared() {
unlock();
lock_shared();
template <typename... A, if_<ap_<m_<SharedLockState>, A...>> = 0>
b<UpgradeLockState> try_unlock_shared_and_lock_upgrade(A const&... a) {
return transition_0_(q::unlock_shared, q::try_lock_upgrade, a...);
}
template <typename... A, if_<ap_<md_<SharedLockState>, A...>> = 0>
b<UpgradeLockState> try_unlock_shared_and_lock_upgrade_for(A const&... a) {
return transition_1_(q::unlock_shared, q::try_lock_upgrade_for, a...);
}
template <typename... A, if_<ap_<mt_<SharedLockState>, A...>> = 0>
b<UpgradeLockState> try_unlock_shared_and_lock_upgrade_until(A const&... a) {
return transition_1_(q::unlock_shared, q::try_lock_upgrade_until, a...);
}
void unlock_and_lock_upgrade() {
unlock();
lock_upgrade();
template <typename... A, if_<ap_<m_<SharedLockState>, A...>> = 0>
b<UniqueLockState> try_unlock_shared_and_lock(A const&... a) {
return transition_0_(q::unlock_shared, q::try_lock, a...);
}
void unlock_upgrade_and_lock() {
unlock_upgrade();
lock();
template <typename... A, if_<ap_<md_<SharedLockState>, A...>> = 0>
b<UniqueLockState> try_unlock_shared_and_lock_for(A const&... a) {
return transition_1_(q::unlock_shared, q::try_lock_for, a...);
}
void unlock_upgrade_and_lock_shared() {
unlock_upgrade();
lock_shared();
template <typename... A, if_<ap_<mt_<SharedLockState>, A...>> = 0>
b<UniqueLockState> try_unlock_shared_and_lock_until(A const&... a) {
return transition_1_(q::unlock_shared, q::try_lock_until, a...);
}
bool try_unlock_upgrade_and_lock() {
return try_unlock_upgrade_and_lock_for(Clock::duration::zero());
template <typename... A, if_<ap_<m_<UpgradeLockState>, A...>> = 0>
b<UniqueLockState> try_unlock_upgrade_and_lock(A const&... a) {
return transition_0_(q::unlock_upgrade, q::try_lock, a...);
}
bool try_unlock_upgrade_and_lock_for(Clock::duration timeout) {
return try_unlock_upgrade_and_lock_until(now + timeout);
template <typename... A, if_<ap_<md_<UpgradeLockState>, A...>> = 0>
b<UniqueLockState> try_unlock_upgrade_and_lock_for(A const&... a) {
return transition_1_(q::unlock_upgrade, q::try_lock_for, a...);
}
bool try_unlock_upgrade_and_lock_until(Clock::time_point deadline) {
unlock_upgrade();
return try_lock_until(deadline);
template <typename... A, if_<ap_<mt_<UpgradeLockState>, A...>> = 0>
b<UniqueLockState> try_unlock_upgrade_and_lock_until(A const&... a) {
return transition_1_(q::unlock_upgrade, q::try_lock_until, a...);
}
private:
// impl ...
template <bool V>
struct s_ {
s_() = default;
template <typename S, if_<std::is_constructible_v<bool, S>> = 0>
/* implicit */ s_(S const& s) {
!!s == V ? void() : folly::throw_exception<MismatchError>();
}
};
void op(Held h) { try_op(h) || (throw DeadlockError(), 0); }
bool try_op(Held h) { return try_op_for(h, Clock::duration::zero()); }
bool try_op_for(Held h, Clock::duration timeout) {
......@@ -122,143 +246,273 @@ struct Mutex {
locked_until = locked ? Clock::time_point::max() : locked_until;
return locked;
}
void unop(Held h) {
void unop(Held h, s_<1> = {}) {
held == Held::None && (throw UnownedError(), 0);
held == h || (throw MismatchError(), 0);
locked_until == Clock::time_point::min() && (throw UnownedError(), 0);
held = Held::None;
locked_until = Clock::time_point::min();
}
template <size_t... I, typename... A>
decltype(auto) init_(std::index_sequence<I...>, A&&... a) {
auto t = std::forward_as_tuple(static_cast<A&&>(a)...);
return std::forward_as_tuple(std::get<I>(t)...);
}
template <typename Unlock, typename Relock, typename... A>
auto transition_0_(Unlock u, Relock r, A const&... a) {
return u(*this, a...), r(*this);
}
template <typename Unlock, typename Relock, typename... A>
auto transition_1_(Unlock u, Relock r, A const&... a) {
static_assert(sizeof...(A) > 0);
auto last = std::get<sizeof...(A) - 1>(std::forward_as_tuple(a...));
auto seq = std::make_index_sequence<sizeof...(A)>{};
return std::apply(u, init_(seq, *this, a...)), r(*this, last);
}
};
bool is_locked_upgrade(Mutex& m) {
return m.locked_until != Clock::time_point::min();
}
template <Held>
class LockState {
public:
constexpr LockState() = default;
explicit constexpr LockState(bool held) noexcept : held_{held} {}
constexpr LockState(LockState const&) = default;
constexpr LockState& operator=(LockState const&) = default;
explicit constexpr operator bool() const { return held_; }
private:
bool held_{false};
};
using UniqueLockState = LockState<Held::Unique>;
using SharedLockState = LockState<Held::Shared>;
using UpgradeLockState = LockState<Held::Upgrade>;
} // namespace
TEST_F(LockTest, unique_lock) {
Mutex m;
std::ignore = std::unique_lock(m);
std::ignore = std::unique_lock(m, std::try_to_lock);
std::ignore = std::unique_lock(m, std::defer_lock);
m.lock();
std::ignore = std::unique_lock(m, std::adopt_lock);
std::ignore = std::unique_lock(m, 1s);
std::ignore = std::unique_lock(m, m.now + 1s);
}
TEST_F(LockTest, shared_lock) {
Mutex m;
std::ignore = std::shared_lock(m);
std::ignore = std::shared_lock(m, std::try_to_lock);
std::ignore = std::shared_lock(m, std::defer_lock);
m.lock_shared();
std::ignore = std::shared_lock(m, std::adopt_lock);
std::ignore = std::shared_lock(m, 1s);
std::ignore = std::shared_lock(m, m.now + 1s);
}
TEST_F(LockTest, upgrade_lock_construct_default) {
folly::upgrade_lock<Mutex> l;
namespace std {
template <typename X, typename S, typename U>
class unique_lock<Mutex<X, S, U>>
: public folly::unique_lock_base<Mutex<X, S, U>> {
using folly::unique_lock_base<Mutex<X, S, U>>::unique_lock_base;
};
template <typename X, typename S, typename U>
class shared_lock<Mutex<X, S, U>>
: public folly::shared_lock_base<Mutex<X, S, U>> {
using folly::shared_lock_base<Mutex<X, S, U>>::shared_lock_base;
};
} // namespace std
// general helpers for use across test types
template <typename Param>
using param_mutex_t = Mutex<
typename Param::unique_lock_state,
typename Param::shared_lock_state,
typename Param::upgrade_lock_state>;
template <typename Param>
using param_lock_t = typename Param::template lock_type<param_mutex_t<Param>>;
template <typename Param>
using param_state_t = folly::detail::lock_state_type_of_t<param_lock_t<Param>>;
template <typename Param>
using param_from_lock_t =
typename Param::template from_lock_type<param_mutex_t<Param>>;
template <typename Param>
using param_to_lock_t =
typename Param::template to_lock_type<param_mutex_t<Param>>;
template <typename L>
[[maybe_unused]] static constexpr Held held_v = Held::None;
template <typename M>
static constexpr Held held_v<folly::unique_lock<M>> = Held::Unique;
template <typename M>
static constexpr Held held_v<folly::shared_lock<M>> = Held::Shared;
template <typename M>
static constexpr Held held_v<folly::upgrade_lock<M>> = Held::Upgrade;
template <typename M>
using x = folly::unique_lock<M>;
template <typename M>
using s = folly::shared_lock<M>;
template <typename M>
using u = folly::upgrade_lock<M>;
// combinatorial test suite for lock types
//
// combinations:
// - lock is: x (unique), s (shared), u (upgrade)?
// - lock has state?
//
// lower x, s, u denote locks sans state
// upper X, S, U denote locks with state
template <int X, int S, int U, template <typename> class L>
struct LockTestParam {
using unique_lock_state = std::conditional_t<X, UniqueLockState, void>;
using shared_lock_state = std::conditional_t<S, SharedLockState, void>;
using upgrade_lock_state = std::conditional_t<U, UpgradeLockState, void>;
template <typename M>
using lock_type = L<M>;
};
template <typename Param>
struct LockTest : testing::TestWithParam<Param> {};
TYPED_TEST_SUITE_P(LockTest);
TYPED_TEST_P(LockTest, ctor) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
using state_type = param_state_t<TypeParam>;
mutex_type m;
std::ignore = lock_type{m};
std::ignore = lock_type{m, std::try_to_lock};
std::ignore = lock_type{m, std::defer_lock};
lock_type l{m};
if constexpr (std::is_void_v<state_type>) {
std::ignore = lock_type{*l.release(), std::adopt_lock};
} else {
auto s = l.state();
std::ignore = lock_type{*l.release(), std::adopt_lock, s};
}
std::ignore = lock_type{m, 1s};
std::ignore = lock_type{m, m.now + 1s};
}
TYPED_TEST_P(LockTest, construct_default) {
using lock_type = param_lock_t<TypeParam>;
lock_type l;
EXPECT_EQ(nullptr, l.mutex());
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex) {
Mutex m;
EXPECT_FALSE(is_locked_upgrade(m));
folly::upgrade_lock<Mutex> l{m};
EXPECT_TRUE(is_locked_upgrade(m));
TYPED_TEST_P(LockTest, construct_mutex) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m};
EXPECT_EQ(held_v<lock_type>, m.held);
EXPECT_EQ(&m, l.mutex());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_defer) {
Mutex m;
EXPECT_FALSE(is_locked_upgrade(m));
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
EXPECT_FALSE(is_locked_upgrade(m));
TYPED_TEST_P(LockTest, construct_mutex_defer) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m, std::defer_lock};
EXPECT_EQ(Held::None, m.held);
EXPECT_EQ(&m, l.mutex());
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_try_pass) {
Mutex m;
EXPECT_FALSE(is_locked_upgrade(m));
folly::upgrade_lock<Mutex> l{m, std::try_to_lock};
EXPECT_TRUE(is_locked_upgrade(m));
TYPED_TEST_P(LockTest, construct_mutex_try_pass) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m, std::try_to_lock};
EXPECT_EQ(held_v<lock_type>, m.held);
EXPECT_EQ(&m, l.mutex());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_try_fail) {
Mutex m;
TYPED_TEST_P(LockTest, construct_mutex_try_fail) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = m.now + 1s;
EXPECT_TRUE(is_locked_upgrade(m));
folly::upgrade_lock<Mutex> l{m, std::try_to_lock};
EXPECT_TRUE(is_locked_upgrade(m));
lock_type l{m, std::try_to_lock};
EXPECT_EQ(Held::None, m.held);
EXPECT_EQ(&m, l.mutex());
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_adopt) {
Mutex m;
m.held = Mutex::Held::Upgrade;
TYPED_TEST_P(LockTest, construct_mutex_adopt) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
using state_type = param_state_t<TypeParam>;
mutex_type m;
m.held = held_v<lock_type>;
m.locked_until = m.now + 1s;
EXPECT_TRUE(is_locked_upgrade(m));
folly::upgrade_lock<Mutex> l{m, std::adopt_lock};
EXPECT_TRUE(is_locked_upgrade(m));
lock_type l = std::invoke([&] {
if constexpr (std::is_void_v<state_type>) {
return lock_type{m, std::adopt_lock};
} else {
return lock_type{m, std::adopt_lock, state_type{true}};
}
});
EXPECT_EQ(held_v<lock_type>, m.held);
EXPECT_EQ(&m, l.mutex());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_duration_pass) {
Mutex m;
TYPED_TEST_P(LockTest, construct_mutex_duration_pass) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = m.now + 1s;
EXPECT_TRUE(is_locked_upgrade(m));
folly::upgrade_lock<Mutex> l{m, 1000ms};
EXPECT_TRUE(is_locked_upgrade(m));
lock_type l{m, 1000ms};
EXPECT_EQ(held_v<lock_type>, m.held);
EXPECT_EQ(&m, l.mutex());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_duration_fail) {
Mutex m;
TYPED_TEST_P(LockTest, construct_mutex_duration_fail) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = m.now + 1s;
EXPECT_TRUE(is_locked_upgrade(m));
folly::upgrade_lock<Mutex> l{m, 999ms};
EXPECT_TRUE(is_locked_upgrade(m));
lock_type l{m, 999ms};
EXPECT_EQ(Held::None, m.held);
EXPECT_EQ(&m, l.mutex());
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_time_point_pass) {
Mutex m;
TYPED_TEST_P(LockTest, construct_mutex_time_point_pass) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = m.now + 1s;
EXPECT_TRUE(is_locked_upgrade(m));
folly::upgrade_lock<Mutex> l{m, m.now + 1000ms};
EXPECT_TRUE(is_locked_upgrade(m));
lock_type l{m, m.now + 1000ms};
EXPECT_EQ(held_v<lock_type>, m.held);
EXPECT_EQ(&m, l.mutex());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_time_point_fail) {
Mutex m;
TYPED_TEST_P(LockTest, construct_mutex_time_point_fail) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = m.now + 1s;
EXPECT_TRUE(is_locked_upgrade(m));
folly::upgrade_lock<Mutex> l{m, m.now + 999ms};
EXPECT_TRUE(is_locked_upgrade(m));
lock_type l{m, m.now + 999ms};
EXPECT_EQ(Held::None, m.held);
EXPECT_EQ(&m, l.mutex());
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_move_construct) {
Mutex m;
folly::upgrade_lock<Mutex> l0{m};
TYPED_TEST_P(LockTest, move_construct) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l0{m};
EXPECT_EQ(&m, l0.mutex());
EXPECT_TRUE(l0.owns_lock());
folly::upgrade_lock<Mutex> l1{std::move(l0)};
lock_type l1{std::move(l0)};
EXPECT_EQ(nullptr, l0.mutex());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(&m, l1.mutex());
......@@ -266,192 +520,262 @@ TEST_F(LockTest, upgrade_lock_move_construct) {
l1.unlock();
EXPECT_EQ(&m, l1.mutex());
EXPECT_FALSE(l1.owns_lock());
folly::upgrade_lock<Mutex> l2{std::move(l1)};
lock_type l2{std::move(l1)};
EXPECT_EQ(nullptr, l1.mutex());
EXPECT_FALSE(l1.owns_lock());
EXPECT_EQ(&m, l2.mutex());
EXPECT_FALSE(l2.owns_lock());
}
TEST_F(LockTest, upgrade_lock_destruct) {
Mutex m;
std::unique_ptr<folly::upgrade_lock<Mutex>> lock;
EXPECT_FALSE(is_locked_upgrade(m));
lock = std::make_unique<folly::upgrade_lock<Mutex>>(m);
EXPECT_TRUE(is_locked_upgrade(m));
lock = nullptr;
EXPECT_FALSE(is_locked_upgrade(m));
lock = std::make_unique<folly::upgrade_lock<Mutex>>(m, std::defer_lock);
EXPECT_FALSE(is_locked_upgrade(m));
lock = nullptr;
EXPECT_FALSE(is_locked_upgrade(m));
}
TEST_F(LockTest, upgrade_lock_move_assign) {
Mutex m0;
Mutex m1;
folly::upgrade_lock<Mutex> l0{m0};
TYPED_TEST_P(LockTest, destruct) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
std::optional<lock_type> lock;
lock.emplace(m);
EXPECT_EQ(held_v<lock_type>, m.held);
lock.reset();
EXPECT_EQ(Held::None, m.held);
lock.emplace(m, std::defer_lock);
EXPECT_EQ(Held::None, m.held);
lock.reset();
EXPECT_EQ(Held::None, m.held);
}
TYPED_TEST_P(LockTest, move_assign) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m0;
mutex_type m1;
lock_type l0{m0};
EXPECT_EQ(&m0, l0.mutex());
EXPECT_TRUE(l0.owns_lock());
folly::upgrade_lock<Mutex> l1{m1};
lock_type l1{m1};
EXPECT_EQ(&m1, l1.mutex());
EXPECT_TRUE(l1.owns_lock());
EXPECT_TRUE(is_locked_upgrade(m0));
EXPECT_TRUE(is_locked_upgrade(m1));
EXPECT_EQ(held_v<lock_type>, m0.held);
EXPECT_EQ(held_v<lock_type>, m1.held);
l1 = std::move(l0);
EXPECT_TRUE(is_locked_upgrade(m0));
EXPECT_FALSE(is_locked_upgrade(m1));
EXPECT_EQ(held_v<lock_type>, m0.held);
EXPECT_EQ(Held::None, m1.held);
EXPECT_EQ(nullptr, l0.mutex());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(&m0, l1.mutex());
EXPECT_TRUE(l1.owns_lock());
l1.unlock();
EXPECT_FALSE(is_locked_upgrade(m0));
EXPECT_FALSE(is_locked_upgrade(m1));
EXPECT_EQ(Held::None, m0.held);
EXPECT_EQ(Held::None, m1.held);
}
TEST_F(LockTest, upgrade_lock_pass) {
Mutex m;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
TYPED_TEST_P(LockTest, lock_pass) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m, std::defer_lock};
l.lock();
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_fail) {
Mutex m;
TYPED_TEST_P(LockTest, lock_fail) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = Clock::time_point::max();
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
lock_type l{m, std::defer_lock};
EXPECT_THROW(l.lock(), DeadlockError);
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_owns) {
Mutex m;
folly::upgrade_lock<Mutex> l{m};
TYPED_TEST_P(LockTest, lock_owns) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m};
EXPECT_THROW(l.lock(), std::system_error);
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_empty) {
folly::upgrade_lock<Mutex> l{};
TYPED_TEST_P(LockTest, lock_empty) {
using lock_type = param_lock_t<TypeParam>;
lock_type l{};
EXPECT_THROW(l.lock(), std::system_error);
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_pass) {
Mutex m;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
TYPED_TEST_P(LockTest, try_lock_pass) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m, std::defer_lock};
EXPECT_TRUE(l.try_lock());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_fail) {
Mutex m;
TYPED_TEST_P(LockTest, try_lock_fail) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = m.now + 1s;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
lock_type l{m, std::defer_lock};
EXPECT_FALSE(l.try_lock());
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_owns) {
Mutex m;
folly::upgrade_lock<Mutex> l{m};
TYPED_TEST_P(LockTest, try_lock_owns) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m};
EXPECT_THROW(l.try_lock(), std::system_error);
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_empty) {
folly::upgrade_lock<Mutex> l{};
TYPED_TEST_P(LockTest, try_lock_empty) {
using lock_type = param_lock_t<TypeParam>;
lock_type l{};
EXPECT_THROW(l.try_lock(), std::system_error);
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_for_pass) {
Mutex m;
TYPED_TEST_P(LockTest, try_lock_for_pass) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = m.now + 1s;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
lock_type l{m, std::defer_lock};
EXPECT_TRUE(l.try_lock_for(2s));
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_for_fail) {
Mutex m;
TYPED_TEST_P(LockTest, try_lock_for_fail) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = m.now + 1s;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
lock_type l{m, std::defer_lock};
EXPECT_FALSE(l.try_lock_for(500ms));
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_for_owns) {
Mutex m;
folly::upgrade_lock<Mutex> l{m};
TYPED_TEST_P(LockTest, try_lock_for_owns) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m};
EXPECT_THROW(l.try_lock_for(0s), std::system_error);
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_for_empty) {
folly::upgrade_lock<Mutex> l{};
TYPED_TEST_P(LockTest, try_lock_for_empty) {
using lock_type = param_lock_t<TypeParam>;
lock_type l{};
EXPECT_THROW(l.try_lock_for(0s), std::system_error);
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_until_pass) {
Mutex m;
TYPED_TEST_P(LockTest, try_lock_until_pass) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = m.now + 1s;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
lock_type l{m, std::defer_lock};
EXPECT_TRUE(l.try_lock_until(m.now + 2s));
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_until_fail) {
Mutex m;
TYPED_TEST_P(LockTest, try_lock_until_fail) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
m.locked_until = m.now + 1s;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
lock_type l{m, std::defer_lock};
EXPECT_FALSE(l.try_lock_until(m.now + 500ms));
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_until_owns) {
Mutex m;
folly::upgrade_lock<Mutex> l{m};
TYPED_TEST_P(LockTest, try_lock_until_owns) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m};
EXPECT_THROW(l.try_lock_until(m.now), std::system_error);
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_until_for_empty) {
folly::upgrade_lock<Mutex> l{};
TYPED_TEST_P(LockTest, try_lock_until_empty) {
using lock_type = param_lock_t<TypeParam>;
lock_type l{};
EXPECT_THROW(l.try_lock_until(Clock::now()), std::system_error);
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_unlock_owns) {
Mutex m;
folly::upgrade_lock<Mutex> l{m};
TYPED_TEST_P(LockTest, unlock_owns) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m};
l.unlock();
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_unlock_unlocked) {
Mutex m;
folly::upgrade_lock<Mutex> l{m, std::adopt_lock};
TYPED_TEST_P(LockTest, unlock_unlocked) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
using state_type = param_state_t<TypeParam>;
mutex_type m;
lock_type l = std::invoke([&] {
if constexpr (std::is_void_v<state_type>) {
return lock_type{m, std::adopt_lock};
} else {
return lock_type{m, std::adopt_lock, state_type{true}};
}
});
EXPECT_THROW(l.unlock(), UnownedError);
l.release();
}
TEST_F(LockTest, upgrade_lock_unlock_unowned) {
Mutex m;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
TYPED_TEST_P(LockTest, unlock_unowned) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m, std::defer_lock};
EXPECT_THROW(l.unlock(), std::system_error);
}
TEST_F(LockTest, upgrade_lock_unlock_empty) {
Mutex m;
folly::upgrade_lock<Mutex> l{};
TYPED_TEST_P(LockTest, unlock_empty) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{};
EXPECT_THROW(l.unlock(), std::system_error);
}
TEST_F(LockTest, upgrade_lock_release_empty) {
folly::upgrade_lock<Mutex> l{};
TYPED_TEST_P(LockTest, release_empty) {
using lock_type = param_lock_t<TypeParam>;
lock_type l{};
auto r = l.release();
EXPECT_EQ(nullptr, r);
EXPECT_EQ(nullptr, l.mutex());
......@@ -459,33 +783,42 @@ TEST_F(LockTest, upgrade_lock_release_empty) {
EXPECT_EQ(nullptr, l.release());
}
TEST_F(LockTest, upgrade_lock_release_unowned) {
Mutex m;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
TYPED_TEST_P(LockTest, release_unowned) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m, std::defer_lock};
auto r = l.release();
EXPECT_FALSE(is_locked_upgrade(m));
EXPECT_EQ(Held::None, m.held);
EXPECT_EQ(&m, r);
EXPECT_EQ(nullptr, l.mutex());
EXPECT_FALSE(l.owns_lock());
EXPECT_EQ(nullptr, l.release());
}
TEST_F(LockTest, upgrade_lock_release_owns) {
Mutex m;
folly::upgrade_lock<Mutex> l{m};
TYPED_TEST_P(LockTest, release_owns) {
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m;
lock_type l{m};
auto r = l.release();
EXPECT_TRUE(is_locked_upgrade(m));
EXPECT_EQ(held_v<lock_type>, m.held);
EXPECT_EQ(&m, r);
EXPECT_EQ(nullptr, l.mutex());
EXPECT_FALSE(l.owns_lock());
EXPECT_EQ(nullptr, l.release());
}
TEST_F(LockTest, upgrade_lock_swap) {
Mutex m0;
Mutex m1;
folly::upgrade_lock<Mutex> l0{m0};
folly::upgrade_lock<Mutex> l1{m1, std::defer_lock};
TYPED_TEST_P(LockTest, swap_) { // gtest forces this mangling
using mutex_type = param_mutex_t<TypeParam>;
using lock_type = param_lock_t<TypeParam>;
mutex_type m0;
mutex_type m1;
lock_type l0{m0};
lock_type l1{m1, std::defer_lock};
swap(l0, l1);
EXPECT_EQ(&m1, l0.mutex());
EXPECT_FALSE(l0.owns_lock());
......@@ -493,80 +826,246 @@ TEST_F(LockTest, upgrade_lock_swap) {
EXPECT_TRUE(l1.owns_lock());
}
TEST_F(LockTest, unique_lock_transition_to_shared_lock) {
Mutex m;
std::unique_lock<Mutex> l0{m};
EXPECT_TRUE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Unique, m.held);
std::shared_lock<Mutex> l1 = folly::transition_to_shared_lock(l0);
EXPECT_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Shared, m.held);
}
REGISTER_TYPED_TEST_SUITE_P(
LockTest,
ctor,
construct_default,
construct_mutex,
construct_mutex_defer,
construct_mutex_try_pass,
construct_mutex_try_fail,
construct_mutex_adopt,
construct_mutex_duration_pass,
construct_mutex_duration_fail,
construct_mutex_time_point_pass,
construct_mutex_time_point_fail,
move_construct,
destruct,
move_assign,
lock_pass,
lock_fail,
lock_owns,
lock_empty,
try_lock_pass,
try_lock_fail,
try_lock_owns,
try_lock_empty,
try_lock_for_pass,
try_lock_for_fail,
try_lock_for_owns,
try_lock_for_empty,
try_lock_until_pass,
try_lock_until_fail,
try_lock_until_owns,
try_lock_until_empty,
unlock_owns,
unlock_unlocked,
unlock_unowned,
unlock_empty,
release_empty,
release_unowned,
release_owns,
swap_);
INSTANTIATE_TYPED_TEST_SUITE_P(
x, LockTest, decltype(LockTestParam<0, 0, 0, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
X, LockTest, decltype(LockTestParam<1, 0, 0, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
s, LockTest, decltype(LockTestParam<0, 0, 0, s>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
S, LockTest, decltype(LockTestParam<0, 1, 0, s>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
u, LockTest, decltype(LockTestParam<0, 0, 0, u>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
U, LockTest, decltype(LockTestParam<0, 0, 1, u>{}));
// combinatorial test suite for lock transitions
//
// combinations:
// - from lock is: x (unique), s (shared), u (upgrade)?
// - from lock has state?
// - to lock is: x (unique), s (shared), u (upgrade)?
// - to lock has state?
//
// but limited to valid transitions:
// - x -> s
// - x -> u
// - u -> s
// - u -> x
//
// lower x, s, u denote locks sans state
// upper X, S, U denote locks with state
template <
int X,
int S,
int U,
template <typename>
class FromL,
template <typename>
class ToL>
struct XLockTestParam {
using unique_lock_state = std::conditional_t<X, UniqueLockState, void>;
using shared_lock_state = std::conditional_t<S, SharedLockState, void>;
using upgrade_lock_state = std::conditional_t<U, UpgradeLockState, void>;
template <typename M>
using from_lock_type = FromL<M>;
template <typename M>
using to_lock_type = ToL<M>;
};
TEST_F(LockTest, unique_lock_transition_to_upgrade_lock) {
Mutex m;
std::unique_lock<Mutex> l0{m};
EXPECT_TRUE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Unique, m.held);
folly::upgrade_lock<Mutex> l1 = folly::transition_to_upgrade_lock(l0);
EXPECT_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Upgrade, m.held);
}
template <typename Param>
struct TransitionLockTest : testing::TestWithParam<Param> {};
TYPED_TEST_SUITE_P(TransitionLockTest);
TEST_F(LockTest, upgrade_lock_transition_to_unique_lock) {
Mutex m;
folly::upgrade_lock<Mutex> l0{m};
EXPECT_TRUE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Upgrade, m.held);
std::unique_lock<Mutex> l1 = folly::transition_to_unique_lock(l0);
EXPECT_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Unique, m.held);
}
TYPED_TEST_P(TransitionLockTest, transition) {
using mutex_type = param_mutex_t<TypeParam>;
using from_lock_type = param_from_lock_t<TypeParam>;
using to_lock_type = param_to_lock_t<TypeParam>;
TEST_F(LockTest, upgrade_lock_transition_to_shared_lock) {
Mutex m;
folly::upgrade_lock<Mutex> l0{m};
mutex_type m;
from_lock_type l0{m};
EXPECT_TRUE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Upgrade, m.held);
std::shared_lock<Mutex> l1 = folly::transition_to_shared_lock(l0);
EXPECT_EQ(held_v<from_lock_type>, m.held);
to_lock_type l1 =
folly::transition_lock<TypeParam::template to_lock_type>(l0);
EXPECT_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Shared, m.held);
EXPECT_EQ(held_v<to_lock_type>, m.held);
}
TEST_F(LockTest, upgrade_lock_try_transition_to_unique_lock) {
Mutex m;
folly::upgrade_lock<Mutex> l0{m};
REGISTER_TYPED_TEST_SUITE_P(TransitionLockTest, transition);
INSTANTIATE_TYPED_TEST_SUITE_P(
xs, TransitionLockTest, decltype(XLockTestParam<0, 0, 0, x, s>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
Xs, TransitionLockTest, decltype(XLockTestParam<1, 0, 0, x, s>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
xS, TransitionLockTest, decltype(XLockTestParam<0, 1, 0, x, s>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
XS, TransitionLockTest, decltype(XLockTestParam<1, 1, 0, x, s>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
xu, TransitionLockTest, decltype(XLockTestParam<0, 0, 0, x, u>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
Xu, TransitionLockTest, decltype(XLockTestParam<1, 0, 0, x, u>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
xU, TransitionLockTest, decltype(XLockTestParam<0, 0, 1, x, u>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
XU, TransitionLockTest, decltype(XLockTestParam<1, 0, 1, x, u>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
us, TransitionLockTest, decltype(XLockTestParam<0, 0, 0, u, s>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
Us, TransitionLockTest, decltype(XLockTestParam<0, 0, 1, u, s>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
uS, TransitionLockTest, decltype(XLockTestParam<0, 1, 0, u, s>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
US, TransitionLockTest, decltype(XLockTestParam<0, 1, 1, u, s>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
ux, TransitionLockTest, decltype(XLockTestParam<0, 0, 0, u, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
Ux, TransitionLockTest, decltype(XLockTestParam<0, 0, 1, u, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
uX, TransitionLockTest, decltype(XLockTestParam<1, 0, 0, u, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
UX, TransitionLockTest, decltype(XLockTestParam<1, 0, 1, u, x>{}));
// combinatorial test suite for lock try-transitions
//
// combinations:
// - from lock is: x (unique), s (shared), u (upgrade)?
// - from lock has state?
// - to lock is: x (unique), s (shared), u (upgrade)?
// - to lock has state?
//
// but limited to valid try-transitions:
// - s -> u
// - s -> x
// - u -> x
//
// lower x, s, u denote locks sans state
// upper X, S, U denote locks with state
template <typename Param>
struct TryTransitionLockTest : testing::TestWithParam<Param> {};
TYPED_TEST_SUITE_P(TryTransitionLockTest);
TYPED_TEST_P(TryTransitionLockTest, try_transition) {
using mutex_type = param_mutex_t<TypeParam>;
using from_lock_type = param_from_lock_t<TypeParam>;
using to_lock_type = param_to_lock_t<TypeParam>;
mutex_type m;
from_lock_type l0{m};
EXPECT_TRUE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Upgrade, m.held);
std::unique_lock<Mutex> l1 = folly::try_transition_to_unique_lock(l0);
EXPECT_EQ(held_v<from_lock_type>, m.held);
to_lock_type l1 =
folly::try_transition_lock<TypeParam::template to_lock_type>(l0);
EXPECT_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Unique, m.held);
EXPECT_EQ(held_v<to_lock_type>, m.held);
}
TEST_F(LockTest, upgrade_lock_try_transition_to_unique_lock_for) {
Mutex m;
folly::upgrade_lock<Mutex> l0{m};
TYPED_TEST_P(TryTransitionLockTest, try_transition_for) {
using mutex_type = param_mutex_t<TypeParam>;
using from_lock_type = param_from_lock_t<TypeParam>;
using to_lock_type = param_to_lock_t<TypeParam>;
mutex_type m;
from_lock_type l0{m};
EXPECT_TRUE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Upgrade, m.held);
std::unique_lock<Mutex> l1 = folly::try_transition_to_unique_lock_for(l0, 1s);
EXPECT_EQ(held_v<from_lock_type>, m.held);
to_lock_type l1 =
folly::try_transition_lock_for<TypeParam::template to_lock_type>(l0, 1s);
EXPECT_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Unique, m.held);
EXPECT_EQ(held_v<to_lock_type>, m.held);
}
TEST_F(LockTest, upgrade_lock_try_transition_to_unique_lock_until) {
Mutex m;
folly::upgrade_lock<Mutex> l0{m};
TYPED_TEST_P(TryTransitionLockTest, try_transition_until) {
using mutex_type = param_mutex_t<TypeParam>;
using from_lock_type = param_from_lock_t<TypeParam>;
using to_lock_type = param_to_lock_t<TypeParam>;
mutex_type m;
from_lock_type l0{m};
EXPECT_TRUE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Upgrade, m.held);
std::unique_lock<Mutex> l1 =
folly::try_transition_to_unique_lock_until(l0, m.now + 1s);
EXPECT_EQ(held_v<from_lock_type>, m.held);
to_lock_type l1 =
folly::try_transition_lock_until<TypeParam::template to_lock_type>(
l0, m.now + 1s);
EXPECT_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Unique, m.held);
EXPECT_EQ(held_v<to_lock_type>, m.held);
}
REGISTER_TYPED_TEST_SUITE_P(
TryTransitionLockTest,
try_transition,
try_transition_for,
try_transition_until);
INSTANTIATE_TYPED_TEST_SUITE_P(
su, TryTransitionLockTest, decltype(XLockTestParam<0, 0, 0, s, u>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
Su, TryTransitionLockTest, decltype(XLockTestParam<0, 1, 0, s, u>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
sU, TryTransitionLockTest, decltype(XLockTestParam<0, 0, 1, s, u>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
SU, TryTransitionLockTest, decltype(XLockTestParam<0, 1, 1, s, u>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
sx, TryTransitionLockTest, decltype(XLockTestParam<0, 0, 0, s, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
Sx, TryTransitionLockTest, decltype(XLockTestParam<0, 1, 0, s, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
sX, TryTransitionLockTest, decltype(XLockTestParam<1, 0, 0, s, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
SX, TryTransitionLockTest, decltype(XLockTestParam<1, 1, 0, s, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
ux, TryTransitionLockTest, decltype(XLockTestParam<0, 0, 0, u, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
Ux, TryTransitionLockTest, decltype(XLockTestParam<0, 0, 1, u, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
uX, TryTransitionLockTest, decltype(XLockTestParam<1, 0, 0, u, x>{}));
INSTANTIATE_TYPED_TEST_SUITE_P(
UX, TryTransitionLockTest, decltype(XLockTestParam<1, 0, 1, u, x>{}));
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment