]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | /* |
2 | * This file is open source software, licensed to you under the terms | |
3 | * of the Apache License, Version 2.0 (the "License"). See the NOTICE file | |
4 | * distributed with this work for additional information regarding copyright | |
5 | * ownership. You may not use this file except in compliance with the License. | |
6 | * | |
7 | * You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, | |
12 | * software distributed under the License is distributed on an | |
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
14 | * KIND, either express or implied. See the License for the | |
15 | * specific language governing permissions and limitations | |
16 | * under the License. | |
17 | */ | |
18 | /* | |
19 | * Copyright (C) 2015 Cloudius Systems, Ltd. | |
20 | */ | |
21 | ||
22 | #pragma once | |
23 | ||
24 | #include <seastar/core/semaphore.hh> | |
25 | ||
26 | namespace seastar { | |
27 | ||
28 | /// \cond internal | |
29 | // lock / unlock semantics for rwlock, so it can be used with with_lock() | |
30 | template<typename Clock> | |
31 | class basic_rwlock; | |
32 | ||
33 | template<typename Clock = typename timer<>::clock> | |
34 | struct rwlock_for_read { | |
35 | future<> lock() { | |
36 | return static_cast<basic_rwlock<Clock>*>(this)->read_lock(); | |
37 | } | |
38 | void unlock() { | |
39 | static_cast<basic_rwlock<Clock>*>(this)->read_unlock(); | |
40 | } | |
41 | friend class basic_rwlock<Clock>; | |
42 | }; | |
43 | ||
44 | template<typename Clock = typename timer<>::clock> | |
45 | struct rwlock_for_write { | |
46 | future<> lock() { | |
47 | return static_cast<basic_rwlock<Clock>*>(this)->write_lock(); | |
48 | } | |
49 | void unlock() { | |
50 | static_cast<basic_rwlock<Clock>*>(this)->write_unlock(); | |
51 | } | |
52 | friend class basic_rwlock<Clock>; | |
53 | }; | |
54 | /// \endcond | |
55 | ||
56 | ||
57 | /// \addtogroup fiber-module | |
58 | /// @{ | |
59 | ||
60 | /// Implements a read-write lock mechanism. Beware: this is not a cross-CPU | |
61 | /// lock, due to seastar's sharded architecture. | |
62 | /// Instead, it can be used to achieve rwlock semantics between two (or more) | |
63 | /// fibers running in the same CPU that may use the same resource. | |
64 | /// Acquiring the write lock will effectively cause all readers not to be executed | |
65 | /// until the write part is done. | |
66 | template<typename Clock = typename timer<>::clock> | |
67 | class basic_rwlock : private rwlock_for_read<Clock>, rwlock_for_write<Clock> { | |
68 | using semaphore_type = basic_semaphore<semaphore_default_exception_factory, Clock>; | |
69 | ||
70 | static constexpr size_t max_ops = semaphore_type::max_counter(); | |
71 | ||
72 | semaphore_type _sem; | |
73 | public: | |
74 | basic_rwlock() | |
75 | : _sem(max_ops) { | |
76 | } | |
77 | ||
78 | /// Cast this rwlock into read lock object with lock semantics appropriate to be used | |
79 | /// by "with_lock". The resulting object will have lock / unlock calls that, when called, | |
80 | /// will acquire / release the lock in read mode. | |
81 | rwlock_for_read<Clock>& for_read() { | |
82 | return *this; | |
83 | } | |
84 | ||
85 | /// Cast this rwlock into write lock object with lock semantics appropriate to be used | |
86 | /// by "with_lock". The resulting object will have lock / unlock calls that, when called, | |
87 | /// will acquire / release the lock in write mode. | |
88 | rwlock_for_write<Clock>& for_write() { | |
89 | return *this; | |
90 | } | |
91 | ||
92 | /// Acquires this lock in read mode. Many readers are allowed, but when | |
93 | /// this future returns, and until \ref read_unlock is called, all fibers | |
94 | /// waiting on \ref write_lock are guaranteed not to execute. | |
95 | future<> read_lock(typename semaphore_type::time_point timeout = semaphore_type::time_point::max()) { | |
96 | return _sem.wait(timeout); | |
97 | } | |
98 | ||
99 | /// Releases the lock, which must have been taken in read mode. After this | |
100 | /// is called, one of the fibers waiting on \ref write_lock will be allowed | |
101 | /// to proceed. | |
102 | void read_unlock() { | |
103 | #ifdef SEASTAR_DEBUG_LOCKING | |
104 | assert(_sem.current() < max_ops); | |
105 | #endif | |
106 | _sem.signal(); | |
107 | } | |
108 | ||
109 | /// Acquires this lock in write mode. Only one writer is allowed. When | |
110 | /// this future returns, and until \ref write_unlock is called, all other | |
111 | /// fibers waiting on either \ref read_lock or \ref write_lock are guaranteed | |
112 | /// not to execute. | |
113 | future<> write_lock(typename semaphore_type::time_point timeout = semaphore_type::time_point::max()) { | |
114 | return _sem.wait(timeout, max_ops); | |
115 | } | |
116 | ||
117 | /// Releases the lock, which must have been taken in write mode. After this | |
118 | /// is called, one of the other fibers waiting on \ref write_lock or the fibers | |
119 | /// waiting on \ref read_lock will be allowed to proceed. | |
120 | void write_unlock() { | |
121 | #ifdef SEASTAR_DEBUG_LOCKING | |
122 | assert(_sem.current() == 0); | |
123 | #endif | |
124 | _sem.signal(max_ops); | |
125 | } | |
126 | ||
127 | /// Tries to acquire the lock in read mode iff this can be done without waiting. | |
128 | bool try_read_lock() { | |
129 | return _sem.try_wait(); | |
130 | } | |
131 | ||
132 | /// Tries to acquire the lock in write mode iff this can be done without waiting. | |
133 | bool try_write_lock() { | |
134 | return _sem.try_wait(max_ops); | |
135 | } | |
136 | ||
137 | using holder = semaphore_units<semaphore_default_exception_factory, Clock>; | |
138 | ||
139 | /// hold_read_lock() waits for a read lock and returns an object which, | |
140 | /// when destroyed, releases the lock. This makes it easy to ensure that | |
141 | /// the lock is eventually undone, at any circumstance (even including | |
142 | /// exceptions). The release() method can be used on the returned object | |
143 | /// to release its ownership of the lock and avoid the automatic unlock. | |
144 | /// Note that both hold_read_lock() and hold_write_lock() return an object | |
145 | /// of the same type, rwlock::holder. | |
146 | /// | |
147 | /// hold_read_lock() may throw an exception (or, in other implementations, | |
148 | /// return an exceptional future) when it failed to obtain the lock - | |
149 | /// e.g., on allocation failure. | |
150 | future<holder> hold_read_lock(typename semaphore_type::time_point timeout = semaphore_type::time_point::max()) { | |
151 | return get_units(_sem, 1); | |
152 | } | |
153 | ||
154 | /// hold_write_lock() waits for a write lock and returns an object which, | |
155 | /// when destroyed, releases the lock. This makes it easy to ensure that | |
156 | /// the lock is eventually undone, at any circumstance (even including | |
157 | /// exceptions). The release() method can be used on the returned object | |
158 | /// to release its ownership of the lock and avoid the automatic unlock. | |
159 | /// Note that both hold_read_lock() and hold_write_lock() return an object | |
160 | /// of the same type, rwlock::holder. | |
161 | /// | |
162 | /// hold_read_lock() may throw an exception (or, in other implementations, | |
163 | /// return an exceptional future) when it failed to obtain the lock - | |
164 | /// e.g., on allocation failure. | |
165 | future<holder> hold_write_lock(typename semaphore_type::time_point timeout = semaphore_type::time_point::max()) { | |
166 | return get_units(_sem, max_ops); | |
167 | } | |
168 | ||
169 | /// Checks if any read or write locks are currently held. | |
170 | bool locked() const { | |
171 | return _sem.available_units() != max_ops; | |
172 | } | |
173 | ||
174 | friend class rwlock_for_read<Clock>; | |
175 | friend class rwlock_for_write<Clock>; | |
176 | }; | |
177 | ||
178 | using rwlock = basic_rwlock<>; | |
179 | ||
180 | /// @} | |
181 | ||
182 | } |