]> git.proxmox.com Git - ceph.git/blob - ceph/src/common/shunique_lock.h
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / common / shunique_lock.h
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>
8 #include <shared_mutex>
9 #include <system_error>
10
11 namespace 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.
38 struct acquire_unique_t { };
39
40 /// Acquire shared ownership of the mutex.
41 struct acquire_shared_t { };
42
43 constexpr acquire_unique_t acquire_unique { };
44 constexpr acquire_shared_t acquire_shared { };
45
46 template<typename Mutex>
47 class shunique_lock {
48 public:
49 typedef Mutex mutex_type;
50 typedef std::unique_lock<Mutex> unique_lock_type;
51 typedef std::shared_lock<Mutex> shared_lock_type;
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;
107 break;
108 case ownership::unique:
109 m->unlock();
110 break;
111 case ownership::shared:
112 m->unlock_shared();
113 break;
114 }
115 }
116
117 shunique_lock(shunique_lock const&) = delete;
118 shunique_lock& operator=(shunique_lock const&) = delete;
119
120 shunique_lock(shunique_lock&& l) noexcept : shunique_lock() {
121 swap(l);
122 }
123
124 shunique_lock(unique_lock_type&& l) noexcept {
125 if (l.owns_lock())
126 o = ownership::unique;
127 else
128 o = ownership::none;
129 m = l.release();
130 }
131
132 shunique_lock(shared_lock_type&& l) noexcept {
133 if (l.owns_lock())
134 o = ownership::shared;
135 else
136 o = ownership::none;
137 m = l.release();
138 }
139
140 shunique_lock& operator=(shunique_lock&& l) noexcept {
141 shunique_lock(std::move(l)).swap(*this);
142 return *this;
143 }
144
145 shunique_lock& operator=(unique_lock_type&& l) noexcept {
146 shunique_lock(std::move(l)).swap(*this);
147 return *this;
148 }
149
150 shunique_lock& operator=(shared_lock_type&& l) noexcept {
151 shunique_lock(std::move(l)).swap(*this);
152 return *this;
153 }
154
155 void lock() {
156 lockable();
157 m->lock();
158 o = ownership::unique;
159 }
160
161 void lock_shared() {
162 lockable();
163 m->lock_shared();
164 o = ownership::shared;
165 }
166
167 void lock(ceph::acquire_unique_t) {
168 lock();
169 }
170
171 void lock(ceph::acquire_shared_t) {
172 lock_shared();
173 }
174
175 bool try_lock() {
176 lockable();
177 if (m->try_lock()) {
178 o = ownership::unique;
179 return true;
180 }
181 return false;
182 }
183
184 bool try_lock_shared() {
185 lockable();
186 if (m->try_lock_shared()) {
187 o = ownership::shared;
188 return true;
189 }
190 return false;
191 }
192
193 bool try_lock(ceph::acquire_unique_t) {
194 return try_lock();
195 }
196
197 bool try_lock(ceph::acquire_shared_t) {
198 return try_lock_shared();
199 }
200
201 template<typename Rep, typename Period>
202 bool try_lock_for(const std::chrono::duration<Rep, Period>& dur) {
203 lockable();
204 if (m->try_lock_for(dur)) {
205 o = ownership::unique;
206 return true;
207 }
208 return false;
209 }
210
211 template<typename Rep, typename Period>
212 bool try_lock_shared_for(const std::chrono::duration<Rep, Period>& dur) {
213 lockable();
214 if (m->try_lock_shared_for(dur)) {
215 o = ownership::shared;
216 return true;
217 }
218 return false;
219 }
220
221 template<typename Rep, typename Period>
222 bool try_lock_for(ceph::acquire_unique_t,
223 const std::chrono::duration<Rep, Period>& dur) {
224 return try_lock_for(dur);
225 }
226
227 template<typename Rep, typename Period>
228 bool try_lock_for(ceph::acquire_shared_t,
229 const std::chrono::duration<Rep, Period>& dur) {
230 return try_lock_shared_for(dur);
231 }
232
233 template<typename Clock, typename Duration>
234 bool try_lock_until(const std::chrono::time_point<Clock, Duration>& time) {
235 lockable();
236 if (m->try_lock_until(time)) {
237 o = ownership::unique;
238 return true;
239 }
240 return false;
241 }
242
243 template<typename Clock, typename Duration>
244 bool try_lock_shared_until(const std::chrono::time_point<Clock,
245 Duration>& time) {
246 lockable();
247 if (m->try_lock_shared_until(time)) {
248 o = ownership::shared;
249 return true;
250 }
251 return false;
252 }
253
254 template<typename Clock, typename Duration>
255 bool try_lock_until(ceph::acquire_unique_t,
256 const std::chrono::time_point<Clock, Duration>& time) {
257 return try_lock_until(time);
258 }
259
260 template<typename Clock, typename Duration>
261 bool try_lock_until(ceph::acquire_shared_t,
262 const std::chrono::time_point<Clock, Duration>& time) {
263 return try_lock_shared_until(time);
264 }
265
266 // Only have a single unlock method. Otherwise we'd be building an
267 // Acme lock class suitable only for ravenous coyotes desparate to
268 // devour a road runner. It would be bad. It would be disgusting. It
269 // would be infelicitous as heck. It would leave our developers in a
270 // state of seeming safety unaware of the yawning chasm of failure
271 // that had opened beneath their feet that would soon transition
272 // into a sickening realization of the error they made and a brief
273 // moment of blinking self pity before their program hurled itself
274 // into undefined behaviour and plummeted up the stack with core
275 // dumps trailing behind it.
276
277 void unlock() {
278 switch (o) {
279 case ownership::none:
280 throw std::system_error((int)std::errc::resource_deadlock_would_occur,
281 std::generic_category());
282 break;
283
284 case ownership::unique:
285 m->unlock();
286 break;
287
288 case ownership::shared:
289 m->unlock_shared();
290 break;
291 }
292 o = ownership::none;
293 }
294
295 // Setters
296
297 void swap(shunique_lock& u) noexcept {
298 std::swap(m, u.m);
299 std::swap(o, u.o);
300 }
301
302 mutex_type* release() noexcept {
303 o = ownership::none;
304 mutex_type* tm = m;
305 m = nullptr;
306 return tm;
307 }
308
309 // Ideally I'd rather make a move constructor for std::unique_lock
310 // that took a shunique_lock, but obviously I can't.
311 unique_lock_type release_to_unique() {
312 if (o == ownership::unique) {
313 o = ownership::none;
314 unique_lock_type tu(*m, std::adopt_lock);
315 m = nullptr;
316 return tu;
317 } else if (o == ownership::none) {
318 unique_lock_type tu(*m, std::defer_lock);
319 m = nullptr;
320 return tu;
321 } else if (m == nullptr) {
322 return unique_lock_type();
323 }
324 throw std::system_error((int)std::errc::operation_not_permitted,
325 std::generic_category());
326 }
327
328 shared_lock_type release_to_shared() {
329 if (o == ownership::shared) {
330 o = ownership::none;
331 shared_lock_type ts(*m, std::adopt_lock);
332 m = nullptr;
333 return ts;
334 } else if (o == ownership::none) {
335 shared_lock_type ts(*m, std::defer_lock);
336 m = nullptr;
337 return ts;
338 } else if (m == nullptr) {
339 return shared_lock_type();
340 }
341 throw std::system_error((int)std::errc::operation_not_permitted,
342 std::generic_category());
343 return shared_lock_type();
344 }
345
346 // Getters
347
348 // Note that this returns true if the lock UNIQUE, it will return
349 // false for shared
350 bool owns_lock() const noexcept {
351 return o == ownership::unique;
352 }
353
354 bool owns_lock_shared() const noexcept {
355 return o == ownership::shared;
356 }
357
358 // If you want to make sure you have a lock of some sort on the
359 // mutex, just treat as a bool.
360 explicit operator bool() const noexcept {
361 return o != ownership::none;
362 }
363
364 mutex_type* mutex() const noexcept {
365 return m;
366 }
367
368 private:
369 void lockable() const {
370 if (m == nullptr)
371 throw std::system_error((int)std::errc::operation_not_permitted,
372 std::generic_category());
373 if (o != ownership::none)
374 throw std::system_error((int)std::errc::resource_deadlock_would_occur,
375 std::generic_category());
376 }
377
378 mutex_type* m;
379 enum struct ownership : uint8_t {
380 none, unique, shared
381 };
382 ownership o;
383 };
384 } // namespace ceph
385
386 namespace std {
387 template<typename Mutex>
388 void swap(ceph::shunique_lock<Mutex> sh1,
389 ceph::shunique_lock<Mutex> sha) {
390 sh1.swap(sha);
391 }
392 } // namespace std
393
394 #endif // CEPH_COMMON_SHUNIQUE_LOCK_H