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

Lock utilities

Summary:
[Folly] Lock utilities: `upgrade_lock`, `transition_to_shared_lock`, `transition_to_upgrade_lock`, `transition_to_unique_lock`, `try_transition_to_unique_lock`.

Note: `std::shared_mutex` does not have an upgrade state or any atomic transitions; there is no `std::upgrade_lock`; and `std::shared_lock` does not have a constructor from `std::unique_lock`.

Reviewed By: simpkins, nbronson

Differential Revision: D15532568

fbshipit-source-id: d32f387b26b57539857922c3090dc75ffcaaa538
parent 8c529eea
/*
* 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 <chrono>
#include <mutex>
#include <shared_mutex>
#include <system_error>
#include <folly/Portability.h>
#include <folly/lang/Exception.h>
namespace folly {
template <typename Mutex>
class upgrade_lock {
public:
using mutex_type = Mutex;
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();
}
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)} {}
template <typename Clock, typename Duration>
upgrade_lock(
mutex_type& mutex,
std::chrono::time_point<Clock, Duration> const& deadline)
: mutex_{&mutex}, owns_{mutex.try_lock_upgrade_until(deadline)} {}
~upgrade_lock() {
if (owns_) {
mutex_->unlock_upgrade();
}
}
upgrade_lock& operator=(upgrade_lock&& that) noexcept {
if (owns_lock()) {
unlock();
}
mutex_ = std::exchange(that.mutex_, nullptr);
owns_ = std::exchange(that.owns_, false);
return *this;
}
void lock() {
check<false>();
mutex_->lock_upgrade();
owns_ = true;
}
bool try_lock() {
check<false>();
return owns_ = mutex_->try_lock_upgrade();
}
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);
}
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);
}
void unlock() {
check<true>();
mutex_->unlock_upgrade();
owns_ = false;
}
void swap(upgrade_lock& that) noexcept {
std::swap(mutex_, that.mutex_);
std::swap(owns_, that.owns_);
}
friend void swap(upgrade_lock& a, upgrade_lock& b) noexcept {
a.swap(b);
}
mutex_type* release() noexcept {
owns_ = false;
return std::exchange(mutex_, nullptr);
}
mutex_type* mutex() const noexcept {
return mutex_;
}
bool owns_lock() const noexcept {
return owns_;
}
explicit operator bool() const noexcept {
return owns_;
}
private:
template <bool Owns>
void check() {
if (!mutex_ || owns_ != Owns) {
check_<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));
}
mutex_type* mutex_{nullptr};
bool owns_{false};
};
namespace detail {
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};
}
template <
template <typename> class To,
template <typename> class From,
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;
});
}
} // namespace detail
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();
});
}
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();
});
}
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();
});
}
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();
});
}
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();
});
}
template <typename Mutex, typename Rep, typename Period>
std::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);
});
}
template <typename Mutex, typename Clock, typename Duration>
std::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);
});
}
} // 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.
*/
#include <folly/synchronization/Lock.h>
#include <folly/portability/GTest.h>
using namespace std::literals::chrono_literals;
class LockTest : public testing::Test {};
namespace {
using Clock = std::chrono::steady_clock;
class UnownedError : public std::runtime_error {
public:
UnownedError() : std::runtime_error::runtime_error("unowned") {}
};
class DeadlockError : public std::runtime_error {
public:
DeadlockError() : std::runtime_error::runtime_error("deadlock") {}
};
class MismatchError : public std::runtime_error {
public:
MismatchError() : std::runtime_error::runtime_error("mismatch") {}
};
struct Mutex {
enum class Held { None, Unique, Shared, Upgrade };
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);
}
bool try_lock_until(Clock::time_point deadline) {
return try_op_until(Held::Unique, deadline);
}
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);
}
bool try_lock_shared_until(Clock::time_point deadline) {
return try_op_until(Held::Shared, deadline);
}
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);
}
bool try_lock_upgrade_until(Clock::time_point deadline) {
return try_op_until(Held::Upgrade, deadline);
}
void unlock_upgrade() {
unop(Held::Upgrade);
}
void unlock_and_lock_shared() {
unlock();
lock_shared();
}
void unlock_and_lock_upgrade() {
unlock();
lock_upgrade();
}
void unlock_upgrade_and_lock() {
unlock_upgrade();
lock();
}
void unlock_upgrade_and_lock_shared() {
unlock_upgrade();
lock_shared();
}
bool try_unlock_upgrade_and_lock() {
return try_unlock_upgrade_and_lock_for(Clock::duration::zero());
}
bool try_unlock_upgrade_and_lock_for(Clock::duration timeout) {
return try_unlock_upgrade_and_lock_until(now + timeout);
}
bool try_unlock_upgrade_and_lock_until(Clock::time_point deadline) {
unlock_upgrade();
return try_lock_until(deadline);
}
// impl ...
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) {
return try_op_until(h, now + timeout);
}
bool try_op_until(Held h, Clock::time_point deadline) {
held == Held::None || (throw DeadlockError(), 0);
auto const locked =
locked_until != Clock::time_point::max() && locked_until <= deadline;
held = locked ? h : held;
now = locked ? locked_until : deadline;
locked_until = locked ? Clock::time_point::max() : locked_until;
return locked;
}
void unop(Held h) {
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();
}
};
bool is_locked_upgrade(Mutex& m) {
return m.locked_until != Clock::time_point::min();
}
} // 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;
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));
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));
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));
EXPECT_EQ(&m, l.mutex());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_try_fail) {
Mutex 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));
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;
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));
EXPECT_EQ(&m, l.mutex());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_duration_pass) {
Mutex 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));
EXPECT_EQ(&m, l.mutex());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_duration_fail) {
Mutex 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));
EXPECT_EQ(&m, l.mutex());
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_time_point_pass) {
Mutex 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));
EXPECT_EQ(&m, l.mutex());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_construct_mutex_time_point_fail) {
Mutex 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));
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};
EXPECT_EQ(&m, l0.mutex());
EXPECT_TRUE(l0.owns_lock());
folly::upgrade_lock<Mutex> l1{std::move(l0)};
EXPECT_EQ(nullptr, l0.mutex());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(&m, l1.mutex());
EXPECT_TRUE(l1.owns_lock());
l1.unlock();
EXPECT_EQ(&m, l1.mutex());
EXPECT_FALSE(l1.owns_lock());
folly::upgrade_lock<Mutex> 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};
EXPECT_EQ(&m0, l0.mutex());
EXPECT_TRUE(l0.owns_lock());
folly::upgrade_lock<Mutex> l1{m1};
EXPECT_EQ(&m1, l1.mutex());
EXPECT_TRUE(l1.owns_lock());
EXPECT_TRUE(is_locked_upgrade(m0));
EXPECT_TRUE(is_locked_upgrade(m1));
l1 = std::move(l0);
EXPECT_TRUE(is_locked_upgrade(m0));
EXPECT_FALSE(is_locked_upgrade(m1));
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));
}
TEST_F(LockTest, upgrade_lock_pass) {
Mutex m;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
l.lock();
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_fail) {
Mutex m;
m.locked_until = Clock::time_point::max();
folly::upgrade_lock<Mutex> 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};
EXPECT_THROW(l.lock(), std::system_error);
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_empty) {
folly::upgrade_lock<Mutex> 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};
EXPECT_TRUE(l.try_lock());
EXPECT_TRUE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_fail) {
Mutex m;
m.locked_until = m.now + 1s;
folly::upgrade_lock<Mutex> 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};
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{};
EXPECT_THROW(l.try_lock(), std::system_error);
EXPECT_FALSE(l.owns_lock());
}
TEST_F(LockTest, upgrade_lock_try_lock_for_pass) {
Mutex m;
m.locked_until = m.now + 1s;
folly::upgrade_lock<Mutex> 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;
m.locked_until = m.now + 1s;
folly::upgrade_lock<Mutex> 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};
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{};
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;
m.locked_until = m.now + 1s;
folly::upgrade_lock<Mutex> 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;
m.locked_until = m.now + 1s;
folly::upgrade_lock<Mutex> 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};
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{};
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};
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};
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};
EXPECT_THROW(l.unlock(), std::system_error);
}
TEST_F(LockTest, upgrade_lock_unlock_empty) {
Mutex m;
folly::upgrade_lock<Mutex> l{};
EXPECT_THROW(l.unlock(), std::system_error);
}
TEST_F(LockTest, upgrade_lock_release_empty) {
folly::upgrade_lock<Mutex> l{};
auto r = l.release();
EXPECT_EQ(nullptr, r);
EXPECT_EQ(nullptr, l.mutex());
EXPECT_FALSE(l.owns_lock());
EXPECT_EQ(nullptr, l.release());
}
TEST_F(LockTest, upgrade_lock_release_unowned) {
Mutex m;
folly::upgrade_lock<Mutex> l{m, std::defer_lock};
auto r = l.release();
EXPECT_FALSE(is_locked_upgrade(m));
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};
auto r = l.release();
EXPECT_TRUE(is_locked_upgrade(m));
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};
swap(l0, l1);
EXPECT_EQ(&m1, l0.mutex());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(&m0, l1.mutex());
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);
}
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);
}
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);
}
TEST_F(LockTest, upgrade_lock_transition_to_shared_lock) {
Mutex m;
folly::upgrade_lock<Mutex> 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_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Shared, m.held);
}
TEST_F(LockTest, upgrade_lock_try_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::try_transition_to_unique_lock(l0);
EXPECT_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Unique, m.held);
}
TEST_F(LockTest, upgrade_lock_try_transition_to_unique_lock_for) {
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::try_transition_to_unique_lock_for(l0, 1s);
EXPECT_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Unique, m.held);
}
TEST_F(LockTest, upgrade_lock_try_transition_to_unique_lock_until) {
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::try_transition_to_unique_lock_until(l0, m.now + 1s);
EXPECT_TRUE(l1.owns_lock());
EXPECT_FALSE(l0.owns_lock());
EXPECT_EQ(Mutex::Held::Unique, m.held);
}
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