Commit 541198dc authored by Aaryaman Sagar's avatar Aaryaman Sagar Committed by Facebook Github Bot

Add unique_lock and lock_guard support for DistributedMutex

Summary:
This adds support for the Lockable concept for DistributedMutex through a
unique_lock specialization.  There is also a corresponding lock_guard
specialization.  This should cover 95% of usecases very well.  The other 5%
should probably consider using std::unique_lock, std::lock_guard or
folly::Synchronized

Note that std::unique_lock and std::lock_guard can be specialized.  The
standard does not explicitly prevent specializations for these classes as long
as the specializations are dependent on user-defined classes.  This makes it
ok for us to specialize these two interfaces for our folly mutexes.  See the
quoted paragraph below

Section §[namespace.std]

> A program may add a template specialization for any standard library
> template to namespace std only if the declaration depends on a user-defined
> type and the specialization meets the standard library requirements for the
> original template and is not explicitly prohibited.

The generic lockable wrappers that implement the std::unique_lock and
std::lock_guard interfaces are present in
folly/synchronization/detail/ProxyLockable.h.  The specializations of
std::unique_lock and std::lock_guard in namespace std use these
implementations.  This allows us to stick with a simple specialization for our
mutex, which is well-defined per the paragraph above

Reviewed By: djwatson

Differential Revision: D10377227

fbshipit-source-id: 9f2c50ff5732714c83a79752f58c792e6b2a5e88
parent 0f1c675a
...@@ -229,24 +229,33 @@ inline std::uint64_t recover(std::uint64_t from) { ...@@ -229,24 +229,33 @@ inline std::uint64_t recover(std::uint64_t from) {
template <template <typename> class Atomic, bool TimePublishing> template <template <typename> class Atomic, bool TimePublishing>
class DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy { class DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy {
public: public:
/// DistributedMutexStateProxy is movable, so the caller can contain their // DistributedMutexStateProxy is move constructible and assignable for
/// critical section by moving the proxy around // convenience
DistributedMutexStateProxy(DistributedMutexStateProxy&& other) DistributedMutexStateProxy(DistributedMutexStateProxy&& other) {
: next_{exchange(other.next_, nullptr)}, *this = std::move(other);
expected_{exchange(other.expected_, 0)}, }
wakerMetadata_{exchange(other.wakerMetadata_, {})},
waiters_{exchange(other.waiters_, nullptr)}, DistributedMutexStateProxy& operator=(DistributedMutexStateProxy&& other) {
ready_{exchange(other.ready_, nullptr)} {} DCHECK(!(*this)) << "Cannot move into a valid DistributedMutexStateProxy";
/// The proxy is valid when a mutex acquisition attempt was successful, next_ = exchange(other.next_, nullptr);
/// lock() is guaranteed to return a valid proxy, try_lock() is not expected_ = exchange(other.expected_, 0);
wakerMetadata_ = exchange(other.wakerMetadata_, {});
waiters_ = exchange(other.waiters_, nullptr);
ready_ = exchange(other.ready_, nullptr);
return *this;
}
// The proxy is valid when a mutex acquisition attempt was successful,
// lock() is guaranteed to return a valid proxy, try_lock() is not
explicit operator bool() const { explicit operator bool() const {
return expected_; return expected_;
} }
// private: // private:
/// friend the mutex class, since that will be accessing state private to // friend the mutex class, since that will be accessing state private to
/// this class // this class
friend class DistributedMutex<Atomic, TimePublishing>; friend class DistributedMutex<Atomic, TimePublishing>;
DistributedMutexStateProxy( DistributedMutexStateProxy(
......
...@@ -189,3 +189,4 @@ using DistributedMutex = detail::distributed_mutex::DistributedMutex<>; ...@@ -189,3 +189,4 @@ using DistributedMutex = detail::distributed_mutex::DistributedMutex<>;
} // namespace folly } // namespace folly
#include <folly/synchronization/DistributedMutex-inl.h> #include <folly/synchronization/DistributedMutex-inl.h>
#include <folly/synchronization/DistributedMutexSpecializations.h>
/*
* Copyright 2004-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#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
/*
* Copyright 2004-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/Optional.h>
#include <folly/Portability.h>
#include <cassert>
#include <memory>
#include <mutex>
#include <stdexcept>
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) noexcept {
proxy_.emplace(mutex.lock());
mutex_ = std::addressof(mutex);
}
template <typename Mutex>
ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
ProxyLockableUniqueLock&& a) noexcept {
*this = std::move(a);
}
template <typename Mutex>
ProxyLockableUniqueLock<Mutex>& ProxyLockableUniqueLock<Mutex>::operator=(
ProxyLockableUniqueLock&& other) noexcept {
proxy_ = std::move(other.proxy_);
mutex_ = exchange(other.mutex_, nullptr);
return *this;
}
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);
if (auto state = mutex.try_lock()) {
proxy_.emplace(std::move(state));
}
}
template <typename Mutex>
template <typename Rep, typename Period>
ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
mutex_type& mutex,
const std::chrono::duration<Rep, Period>& duration) {
mutex_ = std::addressof(mutex);
if (auto state = mutex.try_lock_for(duration)) {
proxy_.emplace(std::move(state));
}
}
template <typename Mutex>
template <typename Clock, typename Duration>
ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
mutex_type& mutex,
const std::chrono::time_point<Clock, Duration>& time) {
mutex_ = std::addressof(mutex);
if (auto state = mutex.try_lock_until(time)) {
proxy_.emplace(std::move(state));
}
}
template <typename Mutex>
void ProxyLockableUniqueLock<Mutex>::lock() {
proxylockable_detail::throwIfAlreadyLocked(proxy_);
proxylockable_detail::throwIfNoMutex(mutex_);
proxy_.emplace(mutex_->lock());
}
template <typename Mutex>
void ProxyLockableUniqueLock<Mutex>::unlock() {
proxylockable_detail::throwIfNoMutex(mutex_);
proxylockable_detail::throwIfNotLocked(proxy_);
mutex_->unlock(std::move(*proxy_));
proxy_.reset();
}
template <typename Mutex>
bool ProxyLockableUniqueLock<Mutex>::try_lock() {
proxylockable_detail::throwIfNoMutex(mutex_);
proxylockable_detail::throwIfAlreadyLocked(proxy_);
if (auto state = mutex_->try_lock()) {
proxy_.emplace(std::move(state));
return true;
}
return false;
}
template <typename Mutex>
template <typename Rep, typename Period>
bool ProxyLockableUniqueLock<Mutex>::try_lock_for(
const std::chrono::duration<Rep, Period>& duration) {
proxylockable_detail::throwIfNoMutex(mutex_);
proxylockable_detail::throwIfAlreadyLocked(proxy_);
if (auto state = mutex_->try_lock_for(duration)) {
proxy_.emplace(std::move(state));
return true;
}
return false;
}
template <typename Mutex>
template <typename Clock, typename Duration>
bool ProxyLockableUniqueLock<Mutex>::try_lock_until(
const std::chrono::time_point<Clock, Duration>& time) {
proxylockable_detail::throwIfNoMutex(mutex_);
proxylockable_detail::throwIfAlreadyLocked(proxy_);
if (auto state = mutex_->try_lock_until(time)) {
proxy_.emplace(std::move(state));
return true;
}
return false;
}
template <typename Mutex>
void ProxyLockableUniqueLock<Mutex>::swap(
ProxyLockableUniqueLock& other) noexcept {
std::swap(mutex_, other.mutex_);
std::swap(proxy_, other.proxy_);
}
template <typename Mutex>
typename ProxyLockableUniqueLock<Mutex>::mutex_type*
ProxyLockableUniqueLock<Mutex>::mutex() const noexcept {
return mutex_;
}
template <typename Mutex>
typename ProxyLockableUniqueLock<Mutex>::proxy_type*
ProxyLockableUniqueLock<Mutex>::proxy() const noexcept {
return proxy_ ? std::addressof(proxy_.value()) : nullptr;
}
template <typename Mutex>
bool ProxyLockableUniqueLock<Mutex>::owns_lock() const noexcept {
return proxy_.has_value();
}
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 2004-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/Optional.h>
#include <mutex>
namespace folly {
namespace detail {
/**
* ProxyLockable is a "concept" that is used usually for mutexes that don't
* return void, but rather a proxy 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 proxy_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.
*/
ProxyLockableUniqueLock(mutex_type&) noexcept;
/**
* Explicit locking constructors to control how the lock() method is called
*
* 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::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>& duration);
template <typename Clock, typename Duration>
bool try_lock_until(const std::chrono::time_point<Clock, Duration>& time);
/**
* 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
* proxy() returns a pointer to the contained proxy 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 proxy() returns nullptr
*/
mutex_type* mutex() const noexcept;
proxy_type* proxy() const noexcept;
private:
friend class ProxyLockableTest;
/**
* If the optional has a value, the mutex is locked, if it is empty, it is
* not
*/
mutable folly::Optional<proxy_type> proxy_{};
mutex_type* mutex_{nullptr};
};
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 2018-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/Benchmark.h>
#include <folly/synchronization/detail/ProxyLockable.h>
#include <mutex>
#include <tuple>
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 2018-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/synchronization/detail/ProxyLockable.h>
#include <folly/Benchmark.h>
#include <folly/Random.h>
#include <folly/portability/GTest.h>
#include <folly/synchronization/DistributedMutex.h>
#include <atomic>
#include <chrono>
#include <mutex>
#include <thread>
#include <tuple>
#include <vector>
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.proxy());
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.proxy());
EXPECT_EQ(mutex.locked_, 1);
}
TEST_F(ProxyLockableTest, UniqueLockConstructMoveConstructAssign) {
auto mutex = MockMutex{};
auto one = ProxyLockableUniqueLock<MockMutex>{mutex};
EXPECT_TRUE(one.mutex());
EXPECT_TRUE(one.proxy());
auto two = std::move(one);
EXPECT_FALSE(one.mutex());
EXPECT_FALSE(one.proxy());
EXPECT_FALSE(one.owns_lock());
EXPECT_FALSE(one.operator bool());
EXPECT_TRUE(two.mutex());
EXPECT_TRUE(two.proxy());
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.proxy());
EXPECT_FALSE(one.proxy());
EXPECT_FALSE(one.proxy());
EXPECT_EQ(mutex.locked_, 1);
}
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.proxy());
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.proxy());
}
} // 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.proxy());
lck.lock();
EXPECT_TRUE(lck.mutex());
EXPECT_TRUE(lck.proxy());
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.proxy());
lck.lock();
EXPECT_TRUE(lck.mutex());
EXPECT_TRUE(lck.proxy());
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.proxy());
EXPECT_TRUE(lck.try_lock());
EXPECT_TRUE(lck.mutex());
EXPECT_TRUE(lck.proxy());
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
...@@ -204,11 +204,10 @@ void basicNThreads(int numThreads, int iterations = FLAGS_stress_factor) { ...@@ -204,11 +204,10 @@ void basicNThreads(int numThreads, int iterations = FLAGS_stress_factor) {
auto&& function = [&](auto id) { auto&& function = [&](auto id) {
return [&, id] { return [&, id] {
for (auto j = 0; j < iterations; ++j) { for (auto j = 0; j < iterations; ++j) {
auto state = mutex.lock(); auto lck = std::unique_lock<std::decay_t<decltype(mutex)>>{mutex};
EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0); EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
result.push_back(id); result.push_back(id);
EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1); EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
mutex.unlock(std::move(state));
} }
}; };
}; };
......
...@@ -50,20 +50,12 @@ static void burn(size_t n) { ...@@ -50,20 +50,12 @@ static void burn(size_t n) {
} }
namespace { namespace {
template <typename Mutex> template <typename Mutex>
std::unique_lock<Mutex> lock(Mutex& mutex) { std::unique_lock<Mutex> lock(Mutex& mutex) {
return std::unique_lock<Mutex>{mutex}; return std::unique_lock<Mutex>{mutex};
} }
template <typename Mutex, typename Other> template <typename Mutex, typename Other>
void unlock(Mutex&, Other) {} void unlock(Mutex&, Other) {}
auto lock(folly::DistributedMutex& mutex) {
return mutex.lock();
}
template <typename State>
void unlock(folly::DistributedMutex& mutex, State state) {
mutex.unlock(std::move(state));
}
struct SimpleBarrier { struct SimpleBarrier {
explicit SimpleBarrier(int count) : count_(count) {} explicit SimpleBarrier(int count) : count_(count) {}
......
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