]>
Commit | Line | Data |
---|---|---|
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 | |
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; | |
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 | ||
367 | private: | |
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 | ||
385 | namespace 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 |