]>
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> | |
f67539c2 TL |
34 | class rwlock_for_read { |
35 | public: | |
11fdf7f2 TL |
36 | future<> lock() { |
37 | return static_cast<basic_rwlock<Clock>*>(this)->read_lock(); | |
38 | } | |
39 | void unlock() { | |
40 | static_cast<basic_rwlock<Clock>*>(this)->read_unlock(); | |
41 | } | |
42 | friend class basic_rwlock<Clock>; | |
43 | }; | |
44 | ||
45 | template<typename Clock = typename timer<>::clock> | |
f67539c2 TL |
46 | class rwlock_for_write { |
47 | public: | |
11fdf7f2 TL |
48 | future<> lock() { |
49 | return static_cast<basic_rwlock<Clock>*>(this)->write_lock(); | |
50 | } | |
51 | void unlock() { | |
52 | static_cast<basic_rwlock<Clock>*>(this)->write_unlock(); | |
53 | } | |
54 | friend class basic_rwlock<Clock>; | |
55 | }; | |
56 | /// \endcond | |
57 | ||
58 | ||
59 | /// \addtogroup fiber-module | |
60 | /// @{ | |
61 | ||
62 | /// Implements a read-write lock mechanism. Beware: this is not a cross-CPU | |
63 | /// lock, due to seastar's sharded architecture. | |
64 | /// Instead, it can be used to achieve rwlock semantics between two (or more) | |
65 | /// fibers running in the same CPU that may use the same resource. | |
66 | /// Acquiring the write lock will effectively cause all readers not to be executed | |
67 | /// until the write part is done. | |
68 | template<typename Clock = typename timer<>::clock> | |
69 | class basic_rwlock : private rwlock_for_read<Clock>, rwlock_for_write<Clock> { | |
70 | using semaphore_type = basic_semaphore<semaphore_default_exception_factory, Clock>; | |
71 | ||
72 | static constexpr size_t max_ops = semaphore_type::max_counter(); | |
73 | ||
74 | semaphore_type _sem; | |
75 | public: | |
76 | basic_rwlock() | |
77 | : _sem(max_ops) { | |
78 | } | |
79 | ||
80 | /// Cast this rwlock into read lock object with lock semantics appropriate to be used | |
81 | /// by "with_lock". The resulting object will have lock / unlock calls that, when called, | |
82 | /// will acquire / release the lock in read mode. | |
83 | rwlock_for_read<Clock>& for_read() { | |
84 | return *this; | |
85 | } | |
86 | ||
87 | /// Cast this rwlock into write lock object with lock semantics appropriate to be used | |
88 | /// by "with_lock". The resulting object will have lock / unlock calls that, when called, | |
89 | /// will acquire / release the lock in write mode. | |
90 | rwlock_for_write<Clock>& for_write() { | |
91 | return *this; | |
92 | } | |
93 | ||
94 | /// Acquires this lock in read mode. Many readers are allowed, but when | |
95 | /// this future returns, and until \ref read_unlock is called, all fibers | |
96 | /// waiting on \ref write_lock are guaranteed not to execute. | |
97 | future<> read_lock(typename semaphore_type::time_point timeout = semaphore_type::time_point::max()) { | |
98 | return _sem.wait(timeout); | |
99 | } | |
100 | ||
101 | /// Releases the lock, which must have been taken in read mode. After this | |
102 | /// is called, one of the fibers waiting on \ref write_lock will be allowed | |
103 | /// to proceed. | |
104 | void read_unlock() { | |
11fdf7f2 | 105 | assert(_sem.current() < max_ops); |
11fdf7f2 TL |
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() { | |
11fdf7f2 | 121 | assert(_sem.current() == 0); |
11fdf7f2 TL |
122 | _sem.signal(max_ops); |
123 | } | |
124 | ||
125 | /// Tries to acquire the lock in read mode iff this can be done without waiting. | |
126 | bool try_read_lock() { | |
127 | return _sem.try_wait(); | |
128 | } | |
129 | ||
130 | /// Tries to acquire the lock in write mode iff this can be done without waiting. | |
131 | bool try_write_lock() { | |
132 | return _sem.try_wait(max_ops); | |
133 | } | |
134 | ||
135 | using holder = semaphore_units<semaphore_default_exception_factory, Clock>; | |
136 | ||
137 | /// hold_read_lock() waits for a read lock and returns an object which, | |
138 | /// when destroyed, releases the lock. This makes it easy to ensure that | |
139 | /// the lock is eventually undone, at any circumstance (even including | |
140 | /// exceptions). The release() method can be used on the returned object | |
141 | /// to release its ownership of the lock and avoid the automatic unlock. | |
142 | /// Note that both hold_read_lock() and hold_write_lock() return an object | |
143 | /// of the same type, rwlock::holder. | |
144 | /// | |
145 | /// hold_read_lock() may throw an exception (or, in other implementations, | |
146 | /// return an exceptional future) when it failed to obtain the lock - | |
147 | /// e.g., on allocation failure. | |
148 | future<holder> hold_read_lock(typename semaphore_type::time_point timeout = semaphore_type::time_point::max()) { | |
149 | return get_units(_sem, 1); | |
150 | } | |
151 | ||
152 | /// hold_write_lock() waits for a write lock and returns an object which, | |
153 | /// when destroyed, releases the lock. This makes it easy to ensure that | |
154 | /// the lock is eventually undone, at any circumstance (even including | |
155 | /// exceptions). The release() method can be used on the returned object | |
156 | /// to release its ownership of the lock and avoid the automatic unlock. | |
157 | /// Note that both hold_read_lock() and hold_write_lock() return an object | |
158 | /// of the same type, rwlock::holder. | |
159 | /// | |
160 | /// hold_read_lock() may throw an exception (or, in other implementations, | |
161 | /// return an exceptional future) when it failed to obtain the lock - | |
162 | /// e.g., on allocation failure. | |
163 | future<holder> hold_write_lock(typename semaphore_type::time_point timeout = semaphore_type::time_point::max()) { | |
164 | return get_units(_sem, max_ops); | |
165 | } | |
166 | ||
167 | /// Checks if any read or write locks are currently held. | |
168 | bool locked() const { | |
169 | return _sem.available_units() != max_ops; | |
170 | } | |
171 | ||
172 | friend class rwlock_for_read<Clock>; | |
173 | friend class rwlock_for_write<Clock>; | |
174 | }; | |
175 | ||
176 | using rwlock = basic_rwlock<>; | |
177 | ||
178 | /// @} | |
179 | ||
180 | } |