From: Adam C. Emerson Date: Mon, 14 Sep 2015 17:30:47 +0000 (-0400) Subject: concurrency: Add shunique_lock X-Git-Tag: v10.0.4~16^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=c1b3138f2ec432daeb005130ce465cd11a3258bc;p=ceph.git concurrency: Add shunique_lock Locker class tracking shared/unique/unlocked state of SharedMutex concept. Signed-off-by: Adam C. Emerson --- diff --git a/src/common/Makefile.am b/src/common/Makefile.am index f86d52327fc8..2851cf24ef73 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -277,7 +277,8 @@ noinst_HEADERS += \ common/ceph_time.h \ common/ceph_timer.h \ common/align.h \ - common/mutex_debug.h + common/mutex_debug.h \ + common/shunique_lock.h if ENABLE_XIO noinst_HEADERS += \ diff --git a/src/common/shunique_lock.h b/src/common/shunique_lock.h new file mode 100644 index 000000000000..b7ad08cc10af --- /dev/null +++ b/src/common/shunique_lock.h @@ -0,0 +1,395 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_COMMON_SHUNIQUE_LOCK_H +#define CEPH_COMMON_SHUNIQUE_LOCK_H + +#include +#include +#include + +namespace ceph { +// This is a 'lock' class in the style of shared_lock and +// unique_lock. Like shared_mutex it implements both Lockable and +// SharedLockable. + +// My rationale is thus: one of the advantages of unique_lock is that +// I can pass a thread of execution's control of a lock around as a +// parameter. So that methods further down the call stack can unlock +// it, do something, relock it, and have the lock state be known by +// the caller afterward, explicitly. The shared_lock class offers a +// similar advantage to shared_lock, but each class is one or the +// other. In Objecter we have calls that in most cases need /a/ lock +// on the shared mutex, and whether it's shared or exclusive doesn't +// matter. In some circumstances they may drop the shared lock and +// reacquire an exclusive one. This could be handled by passing both a +// shared and unique lock down the call stack. This is vexacious and +// shameful. + +// Wanting to avoid heaping shame and vexation upon myself, I threw +// this class together. + +// This class makes no attempt to support atomic upgrade or +// downgrade. I don't want either. Matt has convinced me that if you +// think you want them you've usually made a mistake somewhere. It is +// exactly and only a reification of the state held on a shared mutex. + +/// Acquire unique ownership of the mutex. +struct acquire_unique_t { }; + +/// Acquire shared ownership of the mutex. +struct acquire_shared_t { }; + +constexpr acquire_unique_t acquire_unique { }; +constexpr acquire_shared_t acquire_shared { }; + +template +class shunique_lock { +public: + typedef Mutex mutex_type; + typedef std::unique_lock unique_lock_type; + typedef boost::shared_lock shared_lock_type; + + shunique_lock() noexcept : m(nullptr), o(ownership::none) { } + + // We do not provide a default locking/try_locking constructor that + // takes only the mutex, since it is not clear whether to take it + // shared or unique. We explicitly require the use of lock_deferred + // to prevent Nasty Surprises. + + shunique_lock(mutex_type& m, std::defer_lock_t) noexcept + : m(&m), o(ownership::none) { } + + shunique_lock(mutex_type& m, acquire_unique_t) + : m(&m), o(ownership::none) { + lock(); + } + + shunique_lock(mutex_type& m, acquire_shared_t) + : m(&m), o(ownership::none) { + lock_shared(); + } + + template + shunique_lock(mutex_type& m, AcquireType at, std::try_to_lock_t) + : m(&m), o(ownership::none) { + try_lock(at); + } + + shunique_lock(mutex_type& m, acquire_unique_t, std::adopt_lock_t) + : m(&m), o(ownership::unique) { + // You'd better actually have a lock, or I will find you and I + // will hunt you down. + } + + shunique_lock(mutex_type& m, acquire_shared_t, std::adopt_lock_t) + : m(&m), o(ownership::shared) { + } + + template + shunique_lock(mutex_type& m, AcquireType at, + const std::chrono::time_point& t) + : m(&m), o(ownership::none) { + try_lock_until(at, t); + } + + template + shunique_lock(mutex_type& m, AcquireType at, + const std::chrono::duration& dur) + : m(&m), o(ownership::none) { + try_lock_for(at, dur); + } + + ~shunique_lock() { + switch (o) { + case ownership::none: + return; + break; + case ownership::unique: + m->unlock(); + break; + case ownership::shared: + m->unlock_shared(); + break; + } + } + + shunique_lock(shunique_lock const&) = delete; + shunique_lock& operator=(shunique_lock const&) = delete; + + shunique_lock(shunique_lock&& l) noexcept : shunique_lock() { + swap(l); + } + + shunique_lock(unique_lock_type&& l) noexcept { + if (l.owns_lock()) + o = ownership::unique; + else + o = ownership::none; + m = l.release(); + } + + shunique_lock(shared_lock_type&& l) noexcept { + if (l.owns_lock()) + o = ownership::shared; + else + o = ownership::none; + m = l.release(); + } + + shunique_lock& operator=(shunique_lock&& l) noexcept { + shunique_lock(std::move(l)).swap(*this); + return *this; + } + + shunique_lock& operator=(unique_lock_type&& l) noexcept { + shunique_lock(std::move(l)).swap(*this); + return *this; + } + + shunique_lock& operator=(shared_lock_type&& l) noexcept { + shunique_lock(std::move(l)).swap(*this); + return *this; + } + + void lock() { + lockable(); + m->lock(); + o = ownership::unique; + } + + void lock_shared() { + lockable(); + m->lock_shared(); + o = ownership::shared; + } + + void lock(ceph::acquire_unique_t) { + lock(); + } + + void lock(ceph::acquire_shared_t) { + lock_shared(); + } + + bool try_lock() { + lockable(); + if (m->try_lock()) { + o = ownership::unique; + return true; + } + return false; + } + + bool try_lock_shared() { + lockable(); + if (m->try_lock_shared()) { + o = ownership::shared; + return true; + } + return false; + } + + bool try_lock(ceph::acquire_unique_t) { + return try_lock(); + } + + bool try_lock(ceph::acquire_shared_t) { + return try_lock_shared(); + } + + template + bool try_lock_for(const std::chrono::duration& dur) { + lockable(); + if (m->try_lock_for(dur)) { + o = ownership::unique; + return true; + } + return false; + } + + template + bool try_lock_shared_for(const std::chrono::duration& dur) { + lockable(); + if (m->try_lock_shared_for(dur)) { + o = ownership::shared; + return true; + } + return false; + } + + template + bool try_lock_for(ceph::acquire_unique_t, + const std::chrono::duration& dur) { + return try_lock_for(dur); + } + + template + bool try_lock_for(ceph::acquire_shared_t, + const std::chrono::duration& dur) { + return try_lock_shared_for(dur); + } + + template + bool try_lock_until(const std::chrono::time_point& time) { + lockable(); + if (m->try_lock_until(time)) { + o = ownership::unique; + return true; + } + return false; + } + + template + bool try_lock_shared_until(const std::chrono::time_point& time) { + lockable(); + if (m->try_lock_shared_until(time)) { + o = ownership::shared; + return true; + } + return false; + } + + template + bool try_lock_until(ceph::acquire_unique_t, + const std::chrono::time_point& time) { + return try_lock_until(time); + } + + template + bool try_lock_until(ceph::acquire_shared_t, + const std::chrono::time_point& time) { + return try_lock_shared_until(time); + } + + // Only have a single unlock method. Otherwise we'd be building an + // Acme lock class suitable only for ravenous coyotes desparate to + // devour a road runner. It would be bad. It would be disgusting. It + // would be infelicitous as heck. It would leave our developers in a + // state of seeming safety unaware of the yawning chasm of failure + // that had opened beneath their feet that would soon transition + // into a sickening realization of the error they made and a brief + // moment of blinking self pity before their program hurled itself + // into undefined behaviour and plummeted up the stack with core + // dumps trailing behind it. + + void unlock() { + switch (o) { + case ownership::none: + throw std::system_error((int)std::errc::resource_deadlock_would_occur, + std::generic_category()); + break; + + case ownership::unique: + m->unlock(); + break; + + case ownership::shared: + m->unlock_shared(); + break; + } + o = ownership::none; + } + + // Setters + + void swap(shunique_lock& u) noexcept { + std::swap(m, u.m); + std::swap(o, u.o); + } + + mutex_type* release() noexcept { + o = ownership::none; + mutex_type* tm = m; + m = nullptr; + return tm; + } + + // Ideally I'd rather make a move constructor for std::unique_lock + // that took a shunique_lock, but obviously I can't. + unique_lock_type release_to_unique() { + if (o == ownership::unique) { + o = ownership::none; + unique_lock_type tu(*m, std::adopt_lock); + m = nullptr; + return tu; + } else if (o == ownership::none) { + unique_lock_type tu(*m, std::defer_lock); + m = nullptr; + return tu; + } else if (m == nullptr) { + return unique_lock_type(); + } + throw std::system_error((int)std::errc::operation_not_permitted, + std::generic_category()); + return unique_lock_type(); + } + + shared_lock_type release_to_shared() { + if (o == ownership::shared) { + o = ownership::none; + shared_lock_type ts(*m, std::adopt_lock); + m = nullptr; + return ts; + } else if (o == ownership::none) { + shared_lock_type ts(*m, std::defer_lock); + m = nullptr; + return ts; + } else if (m == nullptr) { + return shared_lock_type(); + } + throw std::system_error((int)std::errc::operation_not_permitted, + std::generic_category()); + return shared_lock_type(); + } + + // Getters + + // Note that this returns true if the lock UNIQUE, it will return + // false for shared + bool owns_lock() const noexcept { + return o == ownership::unique; + } + + bool owns_lock_shared() const noexcept { + return o == ownership::shared; + } + + // If you want to make sure you have a lock of some sort on the + // mutex, just treat as a bool. + explicit operator bool() const noexcept { + return o != ownership::none; + } + + mutex_type* mutex() const noexcept { + return m; + } + +private: + void lockable() const { + if (m == nullptr) + throw std::system_error((int)std::errc::operation_not_permitted, + std::generic_category()); + if (o != ownership::none) + throw std::system_error((int)std::errc::resource_deadlock_would_occur, + std::generic_category()); + } + + mutex_type* m; + enum struct ownership : uint8_t { + none, unique, shared + }; + ownership o; +}; +} // namespace ceph + +namespace std { + template + void swap(ceph::shunique_lock sh1, + ceph::shunique_lock sha) { + sh1.swap(sha); + } +} // namespace std + +#endif // CEPH_COMMON_SHUNIQUE_LOCK_H diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 4f1fec6e2ae3..17375eb5415c 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -557,6 +557,19 @@ target_link_libraries(unittest_mutex_debug global set_target_properties(unittest_mutex_debug PROPERTIES COMPILE_FLAGS ${UNITTEST_CXX_FLAGS}) +# unittest_shared_mutex +set(unittest_shunique_lock_srcs + common/test_shunique_lock.cc + ) +add_executable(unittest_shunique_lock + ${unittest_shunique_lock_srcs} + $ + ) +target_link_libraries(unittest_shunique_lock global + ${BLKID_LIBRARIES} ${CMAKE_DL_LIBS} ${TCMALLOC_LIBS} ${UNITTEST_LIBS} ${EXTRALIBS}) +set_target_properties(unittest_shunique_lock + PROPERTIES COMPILE_FLAGS ${UNITTEST_CXX_FLAGS}) + # unittest_sharedptr_registry add_executable(unittest_sharedptr_registry EXCLUDE_FROM_ALL common/test_sharedptr_registry.cc diff --git a/src/test/Makefile.am b/src/test/Makefile.am index 873c69ff7839..ed6a80d56130 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -183,6 +183,11 @@ unittest_mutex_debug_CXXFLAGS = $(UNITTEST_CXXFLAGS) unittest_mutex_debug_LDADD = $(UNITTEST_LDADD) $(CEPH_GLOBAL) ${EXTRALIBS} check_TESTPROGRAMS += unittest_mutex_debug +unittest_shunique_lock_SOURCES = test/common/test_shunique_lock.cc +unittest_shunique_lock_CXXFLAGS = $(UNITTEST_CXXFLAGS) +unittest_shunique_lock_LDADD = $(UNITTEST_LDADD) $(CEPH_GLOBAL) ${EXTRALIBS} +check_TESTPROGRAMS += unittest_shunique_lock + unittest_sharedptr_registry_SOURCES = test/common/test_sharedptr_registry.cc unittest_sharedptr_registry_CXXFLAGS = $(UNITTEST_CXXFLAGS) unittest_sharedptr_registry_LDADD = $(UNITTEST_LDADD) $(CEPH_GLOBAL) diff --git a/src/test/common/test_shunique_lock.cc b/src/test/common/test_shunique_lock.cc new file mode 100644 index 000000000000..77f9708259d2 --- /dev/null +++ b/src/test/common/test_shunique_lock.cc @@ -0,0 +1,576 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 &smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License version 2, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include +#include +#include + +#include + +#include "common/ceph_time.h" +#include "common/shunique_lock.h" + +#include "gtest/gtest.h" + +template +static bool test_try_lock(SharedMutex* sm) { + if (!sm->try_lock()) + return false; + sm->unlock(); + return true; +} + +template +static bool test_try_lock_shared(SharedMutex* sm) { + if (!sm->try_lock_shared()) + return false; + sm->unlock_shared(); + return true; +} + +template +static void check_conflicts(SharedMutex sm, AcquireType) { +} + +template +static void ensure_conflicts(SharedMutex& sm, ceph::acquire_unique_t) { + auto ttl = &test_try_lock; + auto ttls = &test_try_lock_shared; + ASSERT_FALSE(std::async(std::launch::async, ttl, &sm).get()); + ASSERT_FALSE(std::async(std::launch::async, ttls, &sm).get()); +} + +template +static void ensure_conflicts(SharedMutex& sm, ceph::acquire_shared_t) { + auto ttl = &test_try_lock; + auto ttls = &test_try_lock_shared; + ASSERT_FALSE(std::async(std::launch::async, ttl, &sm).get()); + ASSERT_TRUE(std::async(std::launch::async, ttls, &sm).get()); +} + +template +static void ensure_free(SharedMutex& sm) { + auto ttl = &test_try_lock; + auto ttls = &test_try_lock_shared; + ASSERT_TRUE(std::async(std::launch::async, ttl, &sm).get()); + ASSERT_TRUE(std::async(std::launch::async, ttls, &sm).get()); +} + +template +static void check_owns_lock(const SharedMutex& sm, + const ceph::shunique_lock& sul, + AcquireType) { +} + +template +static void check_owns_lock(const SharedMutex& sm, + const ceph::shunique_lock& sul, + ceph::acquire_unique_t) { + ASSERT_TRUE(sul.mutex() == &sm); + ASSERT_TRUE(sul.owns_lock()); + ASSERT_TRUE(!!sul); +} + +template +static void check_owns_lock(const SharedMutex& sm, + const ceph::shunique_lock& sul, + ceph::acquire_shared_t) { + ASSERT_TRUE(sul.owns_lock_shared()); + ASSERT_TRUE(!!sul); +} + +template +static void check_abjures_lock(const SharedMutex& sm, + const ceph::shunique_lock& sul) { + ASSERT_EQ(sul.mutex(), &sm); + ASSERT_FALSE(sul.owns_lock()); + ASSERT_FALSE(sul.owns_lock_shared()); + ASSERT_FALSE(!!sul); +} + +template +static void check_abjures_lock(const ceph::shunique_lock& sul) { + ASSERT_EQ(sul.mutex(), nullptr); + ASSERT_FALSE(sul.owns_lock()); + ASSERT_FALSE(sul.owns_lock_shared()); + ASSERT_FALSE(!!sul); +} + +TEST(ShuniqueLock, DefaultConstructor) { + typedef ceph::shunique_lock shunique_lock; + + shunique_lock l; + + ASSERT_EQ(l.mutex(), nullptr); + ASSERT_FALSE(l.owns_lock()); + ASSERT_FALSE(!!l); + + ASSERT_THROW(l.lock(), std::system_error); + ASSERT_THROW(l.try_lock(), std::system_error); + + ASSERT_THROW(l.lock_shared(), std::system_error); + ASSERT_THROW(l.try_lock_shared(), std::system_error); + + ASSERT_THROW(l.unlock(), std::system_error); + + ASSERT_EQ(l.mutex(), nullptr); + ASSERT_FALSE(l.owns_lock()); + ASSERT_FALSE(!!l); + + ASSERT_EQ(l.release(), nullptr); + + ASSERT_EQ(l.mutex(), nullptr); + ASSERT_FALSE(l.owns_lock()); + ASSERT_FALSE(l.owns_lock_shared()); + ASSERT_FALSE(!!l); +} + +template +void lock_unlock(AcquireType at) { + boost::shared_mutex sm; + typedef ceph::shunique_lock shunique_lock; + + shunique_lock l(sm, at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + + l.unlock(); + + check_abjures_lock(sm, l); + ensure_free(sm); + + l.lock(at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); +} + +TEST(ShuniqueLock, LockUnlock) { + lock_unlock(ceph::acquire_unique); + lock_unlock(ceph::acquire_shared); +} + +template +void lock_destruct(AcquireType at) { + boost::shared_mutex sm; + typedef ceph::shunique_lock shunique_lock; + + { + shunique_lock l(sm, at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + } + + ensure_free(sm); +} + +TEST(ShuniqueLock, LockDestruct) { + lock_destruct(ceph::acquire_unique); + lock_destruct(ceph::acquire_shared); +} + +template +void move_construct(AcquireType at) { + boost::shared_mutex sm; + + typedef ceph::shunique_lock shunique_lock; + + { + shunique_lock l(sm, at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + + shunique_lock o(std::move(l)); + + check_abjures_lock(l); + + check_owns_lock(sm, o, at); + ensure_conflicts(sm, at); + + o.unlock(); + + shunique_lock c(std::move(o)); + + + ASSERT_EQ(o.mutex(), nullptr); + ASSERT_FALSE(!!o); + + check_abjures_lock(sm, c); + + ensure_free(sm); + } +} + +TEST(ShuniqueLock, MoveConstruct) { + move_construct(ceph::acquire_unique); + move_construct(ceph::acquire_shared); + + boost::shared_mutex sm; + { + std::unique_lock ul(sm); + ensure_conflicts(sm, ceph::acquire_unique); + ceph::shunique_lock l(std::move(ul)); + check_owns_lock(sm, l, ceph::acquire_unique); + ensure_conflicts(sm, ceph::acquire_unique); + } + { + std::unique_lock ul(sm, std::defer_lock); + ensure_free(sm); + ceph::shunique_lock l(std::move(ul)); + check_abjures_lock(sm, l); + ensure_free(sm); + } + { + std::unique_lock ul; + ceph::shunique_lock l(std::move(ul)); + check_abjures_lock(l); + } + { + boost::shared_lock sl(sm); + ensure_conflicts(sm, ceph::acquire_shared); + ceph::shunique_lock l(std::move(sl)); + check_owns_lock(sm, l, ceph::acquire_shared); + ensure_conflicts(sm, ceph::acquire_shared); + } + { + boost::shared_lock sl; + ceph::shunique_lock l(std::move(sl)); + check_abjures_lock(l); + } +} + +template +void move_assign(AcquireType at) { + boost::shared_mutex sm; + + typedef ceph::shunique_lock shunique_lock; + + { + shunique_lock l(sm, at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + + shunique_lock o; + + o = std::move(l); + + check_abjures_lock(l); + + check_owns_lock(sm, o, at); + ensure_conflicts(sm, at); + + o.unlock(); + + shunique_lock c(std::move(o)); + + check_abjures_lock(o); + check_abjures_lock(sm, c); + + ensure_free(sm); + + shunique_lock k; + + c = std::move(k); + + check_abjures_lock(k); + check_abjures_lock(c); + + ensure_free(sm); + } +} + +TEST(ShuniqueLock, MoveAssign) { + move_assign(ceph::acquire_unique); + move_assign(ceph::acquire_shared); + + boost::shared_mutex sm; + { + std::unique_lock ul(sm); + ensure_conflicts(sm, ceph::acquire_unique); + ceph::shunique_lock l; + l = std::move(ul); + check_owns_lock(sm, l, ceph::acquire_unique); + ensure_conflicts(sm, ceph::acquire_unique); + } + { + std::unique_lock ul(sm, std::defer_lock); + ensure_free(sm); + ceph::shunique_lock l; + l = std::move(ul); + check_abjures_lock(sm, l); + ensure_free(sm); + } + { + std::unique_lock ul; + ceph::shunique_lock l; + l = std::move(ul); + check_abjures_lock(l); + } + { + boost::shared_lock sl(sm); + ensure_conflicts(sm, ceph::acquire_shared); + ceph::shunique_lock l; + l = std::move(sl); + check_owns_lock(sm, l, ceph::acquire_shared); + ensure_conflicts(sm, ceph::acquire_shared); + } + { + boost::shared_lock sl; + ceph::shunique_lock l; + l = std::move(sl); + check_abjures_lock(l); + } + +} + +template +void construct_deferred(AcquireType at) { + boost::shared_mutex sm; + + typedef ceph::shunique_lock shunique_lock; + + { + shunique_lock l(sm, std::defer_lock); + check_abjures_lock(sm, l); + ensure_free(sm); + + ASSERT_THROW(l.unlock(), std::system_error); + + check_abjures_lock(sm, l); + ensure_free(sm); + + l.lock(at); + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + } + + { + shunique_lock l(sm, std::defer_lock); + check_abjures_lock(sm, l); + ensure_free(sm); + + ASSERT_THROW(l.unlock(), std::system_error); + + check_abjures_lock(sm, l); + ensure_free(sm); + } + ensure_free(sm); +} + +TEST(ShuniqueLock, ConstructDeferred) { + construct_deferred(ceph::acquire_unique); + construct_deferred(ceph::acquire_shared); +} + +template +void construct_try(AcquireType at) { + boost::shared_mutex sm; + typedef ceph::shunique_lock shunique_lock; + + { + shunique_lock l(sm, at, std::try_to_lock); + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + } + + { + std::unique_lock l(sm); + ensure_conflicts(sm, ceph::acquire_unique); + + std::async(std::launch::async, [&sm, at]() { + shunique_lock l(sm, at, std::try_to_lock); + check_abjures_lock(sm, l); + ensure_conflicts(sm, ceph::acquire_unique); + }).get(); + + l.unlock(); + + std::async(std::launch::async, [&sm, at]() { + shunique_lock l(sm, at, std::try_to_lock); + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + }).get(); + } +} + +TEST(ShuniqueLock, ConstructTry) { + construct_try(ceph::acquire_unique); + construct_try(ceph::acquire_shared); +} + +template +void construct_adopt(AcquireType at) { + boost::shared_mutex sm; + + typedef ceph::shunique_lock shunique_lock; + + { + shunique_lock d(sm, at); + d.release(); + } + + ensure_conflicts(sm, at); + + { + shunique_lock l(sm, at, std::adopt_lock); + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + } + + ensure_free(sm); +} + +TEST(ShuniqueLock, ConstructAdopt) { + construct_adopt(ceph::acquire_unique); + construct_adopt(ceph::acquire_shared); +} + +template +void try_lock(AcquireType at) { + boost::shared_mutex sm; + + typedef ceph::shunique_lock shunique_lock; + + { + shunique_lock l(sm, std::defer_lock); + l.try_lock(at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + } + + { + std::unique_lock l(sm); + + std::async(std::launch::async, [&sm, at]() { + shunique_lock l(sm, std::defer_lock); + l.try_lock(at); + + check_abjures_lock(sm, l); + ensure_conflicts(sm, ceph::acquire_unique); + }).get(); + + + l.unlock(); + std::async(std::launch::async, [&sm, at]() { + shunique_lock l(sm, std::defer_lock); + l.try_lock(at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + }).get(); + } +} + +TEST(ShuniqueLock, TryLock) { + try_lock(ceph::acquire_unique); + try_lock(ceph::acquire_shared); +} + +TEST(ShuniqueLock, Release) { + boost::shared_mutex sm; + typedef ceph::shunique_lock shunique_lock; + + { + shunique_lock l(sm, ceph::acquire_unique); + check_owns_lock(sm, l, ceph::acquire_unique); + ensure_conflicts(sm, ceph::acquire_unique); + + l.release(); + check_abjures_lock(l); + ensure_conflicts(sm, ceph::acquire_unique); + } + ensure_conflicts(sm, ceph::acquire_unique); + sm.unlock(); + ensure_free(sm); + + { + shunique_lock l(sm, ceph::acquire_shared); + check_owns_lock(sm, l, ceph::acquire_shared); + ensure_conflicts(sm, ceph::acquire_shared); + + l.release(); + check_abjures_lock(l); + ensure_conflicts(sm, ceph::acquire_shared); + } + ensure_conflicts(sm, ceph::acquire_shared); + sm.unlock_shared(); + ensure_free(sm); + + sm.lock(); + { + shunique_lock l(sm, std::defer_lock); + check_abjures_lock(sm, l); + ensure_conflicts(sm, ceph::acquire_unique); + + l.release(); + check_abjures_lock(l); + ensure_conflicts(sm, ceph::acquire_unique); + } + ensure_conflicts(sm, ceph::acquire_unique); + sm.unlock(); + + ensure_free(sm); + + { + std::unique_lock ul; + shunique_lock l(sm, std::defer_lock); + check_abjures_lock(sm, l); + ensure_free(sm); + + ASSERT_NO_THROW(ul = l.release_to_unique()); + check_abjures_lock(l); + ASSERT_EQ(ul.mutex(), &sm); + ASSERT_FALSE(ul.owns_lock()); + ensure_free(sm); + } + ensure_free(sm); + + { + std::unique_lock ul; + shunique_lock l; + check_abjures_lock(l); + + ASSERT_NO_THROW(ul = l.release_to_unique()); + check_abjures_lock(l); + ASSERT_EQ(ul.mutex(), nullptr); + ASSERT_FALSE(ul.owns_lock()); + } +} + +TEST(ShuniqueLock, NoRecursion) { + boost::shared_mutex sm; + + typedef ceph::shunique_lock shunique_lock; + + { + shunique_lock l(sm, ceph::acquire_unique); + ASSERT_THROW(l.lock(), std::system_error); + ASSERT_THROW(l.try_lock(), std::system_error); + ASSERT_THROW(l.lock_shared(), std::system_error); + ASSERT_THROW(l.try_lock_shared(), std::system_error); + } + + { + shunique_lock l(sm, ceph::acquire_shared); + ASSERT_THROW(l.lock(), std::system_error); + ASSERT_THROW(l.try_lock(), std::system_error); + ASSERT_THROW(l.lock_shared(), std::system_error); + ASSERT_THROW(l.try_lock_shared(), std::system_error); + } +}