1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #ifndef CEPH_COMMON_SHUNIQUE_LOCK_H
5 #define CEPH_COMMON_SHUNIQUE_LOCK_H
8 #include <shared_mutex>
9 #include <system_error>
12 // This is a 'lock' class in the style of shared_lock and
13 // unique_lock. Like shared_mutex it implements both Lockable and
16 // My rationale is thus: one of the advantages of unique_lock is that
17 // I can pass a thread of execution's control of a lock around as a
18 // parameter. So that methods further down the call stack can unlock
19 // it, do something, relock it, and have the lock state be known by
20 // the caller afterward, explicitly. The shared_lock class offers a
21 // similar advantage to shared_lock, but each class is one or the
22 // other. In Objecter we have calls that in most cases need /a/ lock
23 // on the shared mutex, and whether it's shared or exclusive doesn't
24 // matter. In some circumstances they may drop the shared lock and
25 // reacquire an exclusive one. This could be handled by passing both a
26 // shared and unique lock down the call stack. This is vexacious and
29 // Wanting to avoid heaping shame and vexation upon myself, I threw
30 // this class together.
32 // This class makes no attempt to support atomic upgrade or
33 // downgrade. I don't want either. Matt has convinced me that if you
34 // think you want them you've usually made a mistake somewhere. It is
35 // exactly and only a reification of the state held on a shared mutex.
37 /// Acquire unique ownership of the mutex.
38 struct acquire_unique_t
{ };
40 /// Acquire shared ownership of the mutex.
41 struct acquire_shared_t
{ };
43 constexpr acquire_unique_t acquire_unique
{ };
44 constexpr acquire_shared_t acquire_shared
{ };
46 template<typename Mutex
>
49 typedef Mutex mutex_type
;
50 typedef std::unique_lock
<Mutex
> unique_lock_type
;
51 typedef std::shared_lock
<Mutex
> shared_lock_type
;
53 shunique_lock() noexcept
: m(nullptr), o(ownership::none
) { }
55 // We do not provide a default locking/try_locking constructor that
56 // takes only the mutex, since it is not clear whether to take it
57 // shared or unique. We explicitly require the use of lock_deferred
58 // to prevent Nasty Surprises.
60 shunique_lock(mutex_type
& m
, std::defer_lock_t
) noexcept
61 : m(&m
), o(ownership::none
) { }
63 shunique_lock(mutex_type
& m
, acquire_unique_t
)
64 : m(&m
), o(ownership::none
) {
68 shunique_lock(mutex_type
& m
, acquire_shared_t
)
69 : m(&m
), o(ownership::none
) {
73 template<typename AcquireType
>
74 shunique_lock(mutex_type
& m
, AcquireType at
, std::try_to_lock_t
)
75 : m(&m
), o(ownership::none
) {
79 shunique_lock(mutex_type
& m
, acquire_unique_t
, std::adopt_lock_t
)
80 : m(&m
), o(ownership::unique
) {
81 // You'd better actually have a lock, or I will find you and I
82 // will hunt you down.
85 shunique_lock(mutex_type
& m
, acquire_shared_t
, std::adopt_lock_t
)
86 : m(&m
), o(ownership::shared
) {
89 template<typename AcquireType
, typename Clock
, typename Duration
>
90 shunique_lock(mutex_type
& m
, AcquireType at
,
91 const std::chrono::time_point
<Clock
, Duration
>& t
)
92 : m(&m
), o(ownership::none
) {
93 try_lock_until(at
, t
);
96 template<typename AcquireType
, typename Rep
, typename Period
>
97 shunique_lock(mutex_type
& m
, AcquireType at
,
98 const std::chrono::duration
<Rep
, Period
>& dur
)
99 : m(&m
), o(ownership::none
) {
100 try_lock_for(at
, dur
);
105 case ownership::none
:
107 case ownership::unique
:
110 case ownership::shared
:
116 shunique_lock(shunique_lock
const&) = delete;
117 shunique_lock
& operator=(shunique_lock
const&) = delete;
119 shunique_lock(shunique_lock
&& l
) noexcept
: shunique_lock() {
123 shunique_lock(unique_lock_type
&& l
) noexcept
{
125 o
= ownership::unique
;
131 shunique_lock(shared_lock_type
&& l
) noexcept
{
133 o
= ownership::shared
;
139 shunique_lock
& operator=(shunique_lock
&& l
) noexcept
{
140 shunique_lock(std::move(l
)).swap(*this);
144 shunique_lock
& operator=(unique_lock_type
&& l
) noexcept
{
145 shunique_lock(std::move(l
)).swap(*this);
149 shunique_lock
& operator=(shared_lock_type
&& l
) noexcept
{
150 shunique_lock(std::move(l
)).swap(*this);
157 o
= ownership::unique
;
163 o
= ownership::shared
;
166 void lock(ceph::acquire_unique_t
) {
170 void lock(ceph::acquire_shared_t
) {
177 o
= ownership::unique
;
183 bool try_lock_shared() {
185 if (m
->try_lock_shared()) {
186 o
= ownership::shared
;
192 bool try_lock(ceph::acquire_unique_t
) {
196 bool try_lock(ceph::acquire_shared_t
) {
197 return try_lock_shared();
200 template<typename Rep
, typename Period
>
201 bool try_lock_for(const std::chrono::duration
<Rep
, Period
>& dur
) {
203 if (m
->try_lock_for(dur
)) {
204 o
= ownership::unique
;
210 template<typename Rep
, typename Period
>
211 bool try_lock_shared_for(const std::chrono::duration
<Rep
, Period
>& dur
) {
213 if (m
->try_lock_shared_for(dur
)) {
214 o
= ownership::shared
;
220 template<typename Rep
, typename Period
>
221 bool try_lock_for(ceph::acquire_unique_t
,
222 const std::chrono::duration
<Rep
, Period
>& dur
) {
223 return try_lock_for(dur
);
226 template<typename Rep
, typename Period
>
227 bool try_lock_for(ceph::acquire_shared_t
,
228 const std::chrono::duration
<Rep
, Period
>& dur
) {
229 return try_lock_shared_for(dur
);
232 template<typename Clock
, typename Duration
>
233 bool try_lock_until(const std::chrono::time_point
<Clock
, Duration
>& time
) {
235 if (m
->try_lock_until(time
)) {
236 o
= ownership::unique
;
242 template<typename Clock
, typename Duration
>
243 bool try_lock_shared_until(const std::chrono::time_point
<Clock
,
246 if (m
->try_lock_shared_until(time
)) {
247 o
= ownership::shared
;
253 template<typename Clock
, typename Duration
>
254 bool try_lock_until(ceph::acquire_unique_t
,
255 const std::chrono::time_point
<Clock
, Duration
>& time
) {
256 return try_lock_until(time
);
259 template<typename Clock
, typename Duration
>
260 bool try_lock_until(ceph::acquire_shared_t
,
261 const std::chrono::time_point
<Clock
, Duration
>& time
) {
262 return try_lock_shared_until(time
);
265 // Only have a single unlock method. Otherwise we'd be building an
266 // Acme lock class suitable only for ravenous coyotes desparate to
267 // devour a road runner. It would be bad. It would be disgusting. It
268 // would be infelicitous as heck. It would leave our developers in a
269 // state of seeming safety unaware of the yawning chasm of failure
270 // that had opened beneath their feet that would soon transition
271 // into a sickening realization of the error they made and a brief
272 // moment of blinking self pity before their program hurled itself
273 // into undefined behaviour and plummeted up the stack with core
274 // dumps trailing behind it.
278 case ownership::none
:
279 throw std::system_error((int)std::errc::resource_deadlock_would_occur
,
280 std::generic_category());
283 case ownership::unique
:
287 case ownership::shared
:
296 void swap(shunique_lock
& u
) noexcept
{
301 mutex_type
* release() noexcept
{
308 // Ideally I'd rather make a move constructor for std::unique_lock
309 // that took a shunique_lock, but obviously I can't.
310 unique_lock_type
release_to_unique() {
311 if (o
== ownership::unique
) {
313 unique_lock_type
tu(*m
, std::adopt_lock
);
316 } else if (o
== ownership::none
) {
317 unique_lock_type
tu(*m
, std::defer_lock
);
320 } else if (m
== nullptr) {
321 return unique_lock_type();
323 throw std::system_error((int)std::errc::operation_not_permitted
,
324 std::generic_category());
327 shared_lock_type
release_to_shared() {
328 if (o
== ownership::shared
) {
330 shared_lock_type
ts(*m
, std::adopt_lock
);
333 } else if (o
== ownership::none
) {
334 shared_lock_type
ts(*m
, std::defer_lock
);
337 } else if (m
== nullptr) {
338 return shared_lock_type();
340 throw std::system_error((int)std::errc::operation_not_permitted
,
341 std::generic_category());
342 return shared_lock_type();
347 // Note that this returns true if the lock UNIQUE, it will return
349 bool owns_lock() const noexcept
{
350 return o
== ownership::unique
;
353 bool owns_lock_shared() const noexcept
{
354 return o
== ownership::shared
;
357 // If you want to make sure you have a lock of some sort on the
358 // mutex, just treat as a bool.
359 explicit operator bool() const noexcept
{
360 return o
!= ownership::none
;
363 mutex_type
* mutex() const noexcept
{
368 void lockable() const {
370 throw std::system_error((int)std::errc::operation_not_permitted
,
371 std::generic_category());
372 if (o
!= ownership::none
)
373 throw std::system_error((int)std::errc::resource_deadlock_would_occur
,
374 std::generic_category());
378 enum struct ownership
: uint8_t {
386 template<typename Mutex
>
387 void swap(ceph::shunique_lock
<Mutex
> sh1
,
388 ceph::shunique_lock
<Mutex
> sha
) {
393 #endif // CEPH_COMMON_SHUNIQUE_LOCK_H