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
This diff is collapsed.
/*
* 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
This diff is collapsed.
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