2 * Copyright 2016 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
22 // TimedMutex implementation
25 template <typename BatonType>
26 void TimedMutex<BatonType>::lock() {
27 pthread_spin_lock(&lock_);
30 pthread_spin_unlock(&lock_);
34 // Delay constructing the waiter until it is actually required.
35 // This makes a huge difference, at least in the benchmarks,
36 // when the mutex isn't locked.
38 waiters_.push_back(waiter);
39 pthread_spin_unlock(&lock_);
43 template <typename BatonType>
44 template <typename Rep, typename Period>
45 bool TimedMutex<BatonType>::timed_lock(
46 const std::chrono::duration<Rep, Period>& duration) {
47 pthread_spin_lock(&lock_);
50 pthread_spin_unlock(&lock_);
55 waiters_.push_back(waiter);
56 pthread_spin_unlock(&lock_);
58 if (!waiter.baton.timed_wait(duration)) {
59 // We timed out. Two cases:
60 // 1. We're still in the waiter list and we truly timed out
61 // 2. We're not in the waiter list anymore. This could happen if the baton
62 // times out but the mutex is unlocked before we reach this code. In this
63 // case we'll pretend we got the lock on time.
64 pthread_spin_lock(&lock_);
65 if (waiter.hook.is_linked()) {
66 waiters_.erase(waiters_.iterator_to(waiter));
67 pthread_spin_unlock(&lock_);
70 pthread_spin_unlock(&lock_);
75 template <typename BatonType>
76 bool TimedMutex<BatonType>::try_lock() {
77 pthread_spin_lock(&lock_);
79 pthread_spin_unlock(&lock_);
83 pthread_spin_unlock(&lock_);
87 template <typename BatonType>
88 void TimedMutex<BatonType>::unlock() {
89 pthread_spin_lock(&lock_);
90 if (waiters_.empty()) {
92 pthread_spin_unlock(&lock_);
95 MutexWaiter& to_wake = waiters_.front();
98 pthread_spin_unlock(&lock_);
102 // TimedRWMutex implementation
105 template <typename BatonType>
106 void TimedRWMutex<BatonType>::read_lock() {
107 pthread_spin_lock(&lock_);
108 if (state_ == State::WRITE_LOCKED) {
110 read_waiters_.push_back(waiter);
111 pthread_spin_unlock(&lock_);
113 assert(state_ == State::READ_LOCKED);
117 (state_ == State::UNLOCKED && readers_ == 0) ||
118 (state_ == State::READ_LOCKED && readers_ > 0));
119 assert(read_waiters_.empty());
120 state_ = State::READ_LOCKED;
122 pthread_spin_unlock(&lock_);
125 template <typename BatonType>
126 template <typename Rep, typename Period>
127 bool TimedRWMutex<BatonType>::timed_read_lock(
128 const std::chrono::duration<Rep, Period>& duration) {
129 pthread_spin_lock(&lock_);
130 if (state_ == State::WRITE_LOCKED) {
132 read_waiters_.push_back(waiter);
133 pthread_spin_unlock(&lock_);
135 if (!waiter.baton.timed_wait(duration)) {
136 // We timed out. Two cases:
137 // 1. We're still in the waiter list and we truly timed out
138 // 2. We're not in the waiter list anymore. This could happen if the baton
139 // times out but the mutex is unlocked before we reach this code. In
140 // this case we'll pretend we got the lock on time.
141 pthread_spin_lock(&lock_);
142 if (waiter.hook.is_linked()) {
143 read_waiters_.erase(read_waiters_.iterator_to(waiter));
144 pthread_spin_unlock(&lock_);
147 pthread_spin_unlock(&lock_);
152 (state_ == State::UNLOCKED && readers_ == 0) ||
153 (state_ == State::READ_LOCKED && readers_ > 0));
154 assert(read_waiters_.empty());
155 state_ = State::READ_LOCKED;
157 pthread_spin_unlock(&lock_);
161 template <typename BatonType>
162 bool TimedRWMutex<BatonType>::try_read_lock() {
163 pthread_spin_lock(&lock_);
164 if (state_ != State::WRITE_LOCKED) {
166 (state_ == State::UNLOCKED && readers_ == 0) ||
167 (state_ == State::READ_LOCKED && readers_ > 0));
168 assert(read_waiters_.empty());
169 state_ = State::READ_LOCKED;
171 pthread_spin_unlock(&lock_);
174 pthread_spin_unlock(&lock_);
178 template <typename BatonType>
179 void TimedRWMutex<BatonType>::write_lock() {
180 pthread_spin_lock(&lock_);
181 if (state_ == State::UNLOCKED) {
182 verify_unlocked_properties();
183 state_ = State::WRITE_LOCKED;
184 pthread_spin_unlock(&lock_);
188 write_waiters_.push_back(waiter);
189 pthread_spin_unlock(&lock_);
193 template <typename BatonType>
194 template <typename Rep, typename Period>
195 bool TimedRWMutex<BatonType>::timed_write_lock(
196 const std::chrono::duration<Rep, Period>& duration) {
197 pthread_spin_lock(&lock_);
198 if (state_ == State::UNLOCKED) {
199 verify_unlocked_properties();
200 state_ = State::WRITE_LOCKED;
201 pthread_spin_unlock(&lock_);
205 write_waiters_.push_back(waiter);
206 pthread_spin_unlock(&lock_);
208 if (!waiter.baton.timed_wait(duration)) {
209 // We timed out. Two cases:
210 // 1. We're still in the waiter list and we truly timed out
211 // 2. We're not in the waiter list anymore. This could happen if the baton
212 // times out but the mutex is unlocked before we reach this code. In
213 // this case we'll pretend we got the lock on time.
214 pthread_spin_lock(&lock_);
215 if (waiter.hook.is_linked()) {
216 write_waiters_.erase(write_waiters_.iterator_to(waiter));
217 pthread_spin_unlock(&lock_);
220 pthread_spin_unlock(&lock_);
222 assert(state_ == State::WRITE_LOCKED);
226 template <typename BatonType>
227 bool TimedRWMutex<BatonType>::try_write_lock() {
228 pthread_spin_lock(&lock_);
229 if (state_ == State::UNLOCKED) {
230 verify_unlocked_properties();
231 state_ = State::WRITE_LOCKED;
232 pthread_spin_unlock(&lock_);
235 pthread_spin_unlock(&lock_);
239 template <typename BatonType>
240 void TimedRWMutex<BatonType>::unlock() {
241 pthread_spin_lock(&lock_);
242 assert(state_ != State::UNLOCKED);
244 (state_ == State::READ_LOCKED && readers_ > 0) ||
245 (state_ == State::WRITE_LOCKED && readers_ == 0));
246 if (state_ == State::READ_LOCKED) {
250 if (!read_waiters_.empty()) {
252 state_ == State::WRITE_LOCKED && readers_ == 0 &&
253 "read waiters can only accumulate while write locked");
254 state_ = State::READ_LOCKED;
255 readers_ = read_waiters_.size();
257 while (!read_waiters_.empty()) {
258 MutexWaiter& to_wake = read_waiters_.front();
259 read_waiters_.pop_front();
260 to_wake.baton.post();
262 } else if (readers_ == 0) {
263 if (!write_waiters_.empty()) {
264 assert(read_waiters_.empty());
265 state_ = State::WRITE_LOCKED;
267 // Wake a single writer (after releasing the spin lock)
268 MutexWaiter& to_wake = write_waiters_.front();
269 write_waiters_.pop_front();
270 to_wake.baton.post();
272 verify_unlocked_properties();
273 state_ = State::UNLOCKED;
276 assert(state_ == State::READ_LOCKED);
278 pthread_spin_unlock(&lock_);
281 template <typename BatonType>
282 void TimedRWMutex<BatonType>::downgrade() {
283 pthread_spin_lock(&lock_);
284 assert(state_ == State::WRITE_LOCKED && readers_ == 0);
285 state_ = State::READ_LOCKED;
288 if (!read_waiters_.empty()) {
289 readers_ += read_waiters_.size();
291 while (!read_waiters_.empty()) {
292 MutexWaiter& to_wake = read_waiters_.front();
293 read_waiters_.pop_front();
294 to_wake.baton.post();
297 pthread_spin_unlock(&lock_);