]> git.proxmox.com Git - ceph.git/blame - ceph/src/common/shunique_lock.h
import ceph quincy 17.2.4
[ceph.git] / ceph / src / common / shunique_lock.h
CommitLineData
7c673cae
FG
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
3
4#ifndef CEPH_COMMON_SHUNIQUE_LOCK_H
5#define CEPH_COMMON_SHUNIQUE_LOCK_H
6
7#include <mutex>
11fdf7f2 8#include <shared_mutex>
7c673cae 9#include <system_error>
7c673cae
FG
10
11namespace ceph {
12// This is a 'lock' class in the style of shared_lock and
13// unique_lock. Like shared_mutex it implements both Lockable and
14// SharedLockable.
15
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
27// shameful.
28
29// Wanting to avoid heaping shame and vexation upon myself, I threw
30// this class together.
31
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.
36
37/// Acquire unique ownership of the mutex.
38struct acquire_unique_t { };
39
40/// Acquire shared ownership of the mutex.
41struct acquire_shared_t { };
42
43constexpr acquire_unique_t acquire_unique { };
44constexpr acquire_shared_t acquire_shared { };
45
46template<typename Mutex>
47class shunique_lock {
48public:
49 typedef Mutex mutex_type;
50 typedef std::unique_lock<Mutex> unique_lock_type;
11fdf7f2 51 typedef std::shared_lock<Mutex> shared_lock_type;
7c673cae
FG
52
53 shunique_lock() noexcept : m(nullptr), o(ownership::none) { }
54
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.
59
60 shunique_lock(mutex_type& m, std::defer_lock_t) noexcept
61 : m(&m), o(ownership::none) { }
62
63 shunique_lock(mutex_type& m, acquire_unique_t)
64 : m(&m), o(ownership::none) {
65 lock();
66 }
67
68 shunique_lock(mutex_type& m, acquire_shared_t)
69 : m(&m), o(ownership::none) {
70 lock_shared();
71 }
72
73 template<typename AcquireType>
74 shunique_lock(mutex_type& m, AcquireType at, std::try_to_lock_t)
75 : m(&m), o(ownership::none) {
76 try_lock(at);
77 }
78
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.
83 }
84
85 shunique_lock(mutex_type& m, acquire_shared_t, std::adopt_lock_t)
86 : m(&m), o(ownership::shared) {
87 }
88
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);
94 }
95
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);
101 }
102
103 ~shunique_lock() {
104 switch (o) {
105 case ownership::none:
106 return;
7c673cae
FG
107 case ownership::unique:
108 m->unlock();
109 break;
110 case ownership::shared:
111 m->unlock_shared();
112 break;
113 }
114 }
115
116 shunique_lock(shunique_lock const&) = delete;
117 shunique_lock& operator=(shunique_lock const&) = delete;
118
119 shunique_lock(shunique_lock&& l) noexcept : shunique_lock() {
120 swap(l);
121 }
122
123 shunique_lock(unique_lock_type&& l) noexcept {
124 if (l.owns_lock())
125 o = ownership::unique;
126 else
127 o = ownership::none;
128 m = l.release();
129 }
130
131 shunique_lock(shared_lock_type&& l) noexcept {
132 if (l.owns_lock())
133 o = ownership::shared;
134 else
135 o = ownership::none;
136 m = l.release();
137 }
138
139 shunique_lock& operator=(shunique_lock&& l) noexcept {
140 shunique_lock(std::move(l)).swap(*this);
141 return *this;
142 }
143
144 shunique_lock& operator=(unique_lock_type&& l) noexcept {
145 shunique_lock(std::move(l)).swap(*this);
146 return *this;
147 }
148
149 shunique_lock& operator=(shared_lock_type&& l) noexcept {
150 shunique_lock(std::move(l)).swap(*this);
151 return *this;
152 }
153
154 void lock() {
155 lockable();
156 m->lock();
157 o = ownership::unique;
158 }
159
160 void lock_shared() {
161 lockable();
162 m->lock_shared();
163 o = ownership::shared;
164 }
165
166 void lock(ceph::acquire_unique_t) {
167 lock();
168 }
169
170 void lock(ceph::acquire_shared_t) {
171 lock_shared();
172 }
173
174 bool try_lock() {
175 lockable();
176 if (m->try_lock()) {
177 o = ownership::unique;
178 return true;
179 }
180 return false;
181 }
182
183 bool try_lock_shared() {
184 lockable();
185 if (m->try_lock_shared()) {
186 o = ownership::shared;
187 return true;
188 }
189 return false;
190 }
191
192 bool try_lock(ceph::acquire_unique_t) {
193 return try_lock();
194 }
195
196 bool try_lock(ceph::acquire_shared_t) {
197 return try_lock_shared();
198 }
199
200 template<typename Rep, typename Period>
201 bool try_lock_for(const std::chrono::duration<Rep, Period>& dur) {
202 lockable();
203 if (m->try_lock_for(dur)) {
204 o = ownership::unique;
205 return true;
206 }
207 return false;
208 }
209
210 template<typename Rep, typename Period>
211 bool try_lock_shared_for(const std::chrono::duration<Rep, Period>& dur) {
212 lockable();
213 if (m->try_lock_shared_for(dur)) {
214 o = ownership::shared;
215 return true;
216 }
217 return false;
218 }
219
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);
224 }
225
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);
230 }
231
232 template<typename Clock, typename Duration>
233 bool try_lock_until(const std::chrono::time_point<Clock, Duration>& time) {
234 lockable();
235 if (m->try_lock_until(time)) {
236 o = ownership::unique;
237 return true;
238 }
239 return false;
240 }
241
242 template<typename Clock, typename Duration>
243 bool try_lock_shared_until(const std::chrono::time_point<Clock,
244 Duration>& time) {
245 lockable();
246 if (m->try_lock_shared_until(time)) {
247 o = ownership::shared;
248 return true;
249 }
250 return false;
251 }
252
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);
257 }
258
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);
263 }
264
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.
275
276 void unlock() {
277 switch (o) {
278 case ownership::none:
279 throw std::system_error((int)std::errc::resource_deadlock_would_occur,
280 std::generic_category());
281 break;
282
283 case ownership::unique:
284 m->unlock();
285 break;
286
287 case ownership::shared:
288 m->unlock_shared();
289 break;
290 }
291 o = ownership::none;
292 }
293
294 // Setters
295
296 void swap(shunique_lock& u) noexcept {
297 std::swap(m, u.m);
298 std::swap(o, u.o);
299 }
300
301 mutex_type* release() noexcept {
302 o = ownership::none;
303 mutex_type* tm = m;
304 m = nullptr;
305 return tm;
306 }
307
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) {
312 o = ownership::none;
313 unique_lock_type tu(*m, std::adopt_lock);
314 m = nullptr;
315 return tu;
316 } else if (o == ownership::none) {
317 unique_lock_type tu(*m, std::defer_lock);
318 m = nullptr;
319 return tu;
320 } else if (m == nullptr) {
321 return unique_lock_type();
322 }
323 throw std::system_error((int)std::errc::operation_not_permitted,
324 std::generic_category());
7c673cae
FG
325 }
326
327 shared_lock_type release_to_shared() {
328 if (o == ownership::shared) {
329 o = ownership::none;
330 shared_lock_type ts(*m, std::adopt_lock);
331 m = nullptr;
332 return ts;
333 } else if (o == ownership::none) {
334 shared_lock_type ts(*m, std::defer_lock);
335 m = nullptr;
336 return ts;
337 } else if (m == nullptr) {
338 return shared_lock_type();
339 }
340 throw std::system_error((int)std::errc::operation_not_permitted,
341 std::generic_category());
342 return shared_lock_type();
343 }
344
345 // Getters
346
347 // Note that this returns true if the lock UNIQUE, it will return
348 // false for shared
349 bool owns_lock() const noexcept {
350 return o == ownership::unique;
351 }
352
353 bool owns_lock_shared() const noexcept {
354 return o == ownership::shared;
355 }
356
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;
361 }
362
363 mutex_type* mutex() const noexcept {
364 return m;
365 }
366
367private:
368 void lockable() const {
369 if (m == nullptr)
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());
375 }
376
377 mutex_type* m;
378 enum struct ownership : uint8_t {
379 none, unique, shared
380 };
381 ownership o;
382};
383} // namespace ceph
384
385namespace std {
386 template<typename Mutex>
387 void swap(ceph::shunique_lock<Mutex> sh1,
388 ceph::shunique_lock<Mutex> sha) {
389 sh1.swap(sha);
390 }
391} // namespace std
392
393#endif // CEPH_COMMON_SHUNIQUE_LOCK_H