folly copyright 2015 -> copyright 2016
[folly.git] / folly / experimental / fibers / TimedMutex-inl.h
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
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
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 #pragma once
17
18
19 namespace folly { namespace fibers {
20
21 //
22 // TimedMutex implementation
23 //
24
25 template <typename BatonType>
26 void TimedMutex<BatonType>::lock() {
27   pthread_spin_lock(&lock_);
28   if (!locked_) {
29     locked_ = true;
30     pthread_spin_unlock(&lock_);
31     return;
32   }
33
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.
37   MutexWaiter waiter;
38   waiters_.push_back(waiter);
39   pthread_spin_unlock(&lock_);
40   waiter.baton.wait();
41 }
42
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_);
48   if (!locked_) {
49     locked_ = true;
50     pthread_spin_unlock(&lock_);
51     return true;
52   }
53
54   MutexWaiter waiter;
55   waiters_.push_back(waiter);
56   pthread_spin_unlock(&lock_);
57
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_);
68       return false;
69     }
70     pthread_spin_unlock(&lock_);
71   }
72   return true;
73 }
74
75 template <typename BatonType>
76 bool TimedMutex<BatonType>::try_lock() {
77   pthread_spin_lock(&lock_);
78   if (locked_) {
79     pthread_spin_unlock(&lock_);
80     return false;
81   }
82   locked_ = true;
83   pthread_spin_unlock(&lock_);
84   return true;
85 }
86
87 template <typename BatonType>
88 void TimedMutex<BatonType>::unlock() {
89   pthread_spin_lock(&lock_);
90   if (waiters_.empty()) {
91     locked_ = false;
92     pthread_spin_unlock(&lock_);
93     return;
94   }
95   MutexWaiter& to_wake = waiters_.front();
96   waiters_.pop_front();
97   to_wake.baton.post();
98   pthread_spin_unlock(&lock_);
99 }
100
101 //
102 // TimedRWMutex implementation
103 //
104
105 template <typename BatonType>
106 void TimedRWMutex<BatonType>::read_lock() {
107   pthread_spin_lock(&lock_);
108   if (state_ == State::WRITE_LOCKED) {
109     MutexWaiter waiter;
110     read_waiters_.push_back(waiter);
111     pthread_spin_unlock(&lock_);
112     waiter.baton.wait();
113     assert(state_ == State::READ_LOCKED);
114     return;
115   }
116   assert((state_ == State::UNLOCKED && readers_ == 0) ||
117          (state_ == State::READ_LOCKED && readers_ > 0));
118   assert(read_waiters_.empty());
119   state_ = State::READ_LOCKED;
120   readers_ += 1;
121   pthread_spin_unlock(&lock_);
122 }
123
124 template <typename BatonType>
125 template <typename Rep, typename Period>
126 bool TimedRWMutex<BatonType>::timed_read_lock(
127     const std::chrono::duration<Rep, Period>& duration) {
128   pthread_spin_lock(&lock_);
129   if (state_ == State::WRITE_LOCKED) {
130     MutexWaiter waiter;
131     read_waiters_.push_back(waiter);
132     pthread_spin_unlock(&lock_);
133
134     if (!waiter.baton.timed_wait(duration)) {
135       // We timed out. Two cases:
136       // 1. We're still in the waiter list and we truly timed out
137       // 2. We're not in the waiter list anymore. This could happen if the baton
138       //    times out but the mutex is unlocked before we reach this code. In
139       //    this case we'll pretend we got the lock on time.
140       pthread_spin_lock(&lock_);
141       if (waiter.hook.is_linked()) {
142         read_waiters_.erase(read_waiters_.iterator_to(waiter));
143         pthread_spin_unlock(&lock_);
144         return false;
145       }
146       pthread_spin_unlock(&lock_);
147     }
148     return true;
149   }
150   assert((state_ == State::UNLOCKED && readers_ == 0) ||
151          (state_ == State::READ_LOCKED && readers_ > 0));
152   assert(read_waiters_.empty());
153   state_ = State::READ_LOCKED;
154   readers_ += 1;
155   pthread_spin_unlock(&lock_);
156   return true;
157 }
158
159 template <typename BatonType>
160 bool TimedRWMutex<BatonType>::try_read_lock() {
161   pthread_spin_lock(&lock_);
162   if (state_ != State::WRITE_LOCKED) {
163     assert((state_ == State::UNLOCKED && readers_ == 0) ||
164            (state_ == State::READ_LOCKED && readers_ > 0));
165     assert(read_waiters_.empty());
166     state_ = State::READ_LOCKED;
167     readers_ += 1;
168     pthread_spin_unlock(&lock_);
169     return true;
170   }
171   pthread_spin_unlock(&lock_);
172   return false;
173 }
174
175 template <typename BatonType>
176 void TimedRWMutex<BatonType>::write_lock() {
177   pthread_spin_lock(&lock_);
178   if (state_ == State::UNLOCKED) {
179     verify_unlocked_properties();
180     state_ = State::WRITE_LOCKED;
181     pthread_spin_unlock(&lock_);
182     return;
183   }
184   MutexWaiter waiter;
185   write_waiters_.push_back(waiter);
186   pthread_spin_unlock(&lock_);
187   waiter.baton.wait();
188 }
189
190 template <typename BatonType>
191 template <typename Rep, typename Period>
192 bool TimedRWMutex<BatonType>::timed_write_lock(
193     const std::chrono::duration<Rep, Period>& duration) {
194   pthread_spin_lock(&lock_);
195   if (state_ == State::UNLOCKED) {
196     verify_unlocked_properties();
197     state_ = State::WRITE_LOCKED;
198     pthread_spin_unlock(&lock_);
199     return true;
200   }
201   MutexWaiter waiter;
202   write_waiters_.push_back(waiter);
203   pthread_spin_unlock(&lock_);
204
205   if (!waiter.baton.timed_wait(duration)) {
206     // We timed out. Two cases:
207     // 1. We're still in the waiter list and we truly timed out
208     // 2. We're not in the waiter list anymore. This could happen if the baton
209     //    times out but the mutex is unlocked before we reach this code. In
210     //    this case we'll pretend we got the lock on time.
211     pthread_spin_lock(&lock_);
212     if (waiter.hook.is_linked()) {
213       write_waiters_.erase(write_waiters_.iterator_to(waiter));
214       pthread_spin_unlock(&lock_);
215       return false;
216     }
217     pthread_spin_unlock(&lock_);
218   }
219   assert(state_ == State::WRITE_LOCKED);
220   return true;
221 }
222
223 template <typename BatonType>
224 bool TimedRWMutex<BatonType>::try_write_lock() {
225   pthread_spin_lock(&lock_);
226   if (state_ == State::UNLOCKED) {
227     verify_unlocked_properties();
228     state_ = State::WRITE_LOCKED;
229     pthread_spin_unlock(&lock_);
230     return true;
231   }
232   pthread_spin_unlock(&lock_);
233   return false;
234 }
235
236 template <typename BatonType>
237 void TimedRWMutex<BatonType>::unlock() {
238   pthread_spin_lock(&lock_);
239   assert(state_ != State::UNLOCKED);
240   assert((state_ == State::READ_LOCKED && readers_ > 0) ||
241          (state_ == State::WRITE_LOCKED && readers_ == 0));
242   if (state_ == State::READ_LOCKED) {
243     readers_ -= 1;
244   }
245
246   if (!read_waiters_.empty()) {
247     assert(state_ == State::WRITE_LOCKED && readers_ == 0 &&
248            "read waiters can only accumulate while write locked");
249     state_ = State::READ_LOCKED;
250     readers_ = read_waiters_.size();
251
252     while (!read_waiters_.empty()) {
253       MutexWaiter& to_wake = read_waiters_.front();
254       read_waiters_.pop_front();
255       to_wake.baton.post();
256     }
257   } else if (readers_ == 0) {
258     if (!write_waiters_.empty()) {
259       assert(read_waiters_.empty());
260       state_ = State::WRITE_LOCKED;
261
262       // Wake a single writer (after releasing the spin lock)
263       MutexWaiter& to_wake = write_waiters_.front();
264       write_waiters_.pop_front();
265       to_wake.baton.post();
266     } else {
267       verify_unlocked_properties();
268       state_ = State::UNLOCKED;
269     }
270   } else {
271     assert(state_ == State::READ_LOCKED);
272   }
273   pthread_spin_unlock(&lock_);
274 }
275
276 template <typename BatonType>
277 void TimedRWMutex<BatonType>::downgrade() {
278   pthread_spin_lock(&lock_);
279   assert(state_ == State::WRITE_LOCKED && readers_ == 0);
280   state_ = State::READ_LOCKED;
281   readers_ += 1;
282
283   if (!read_waiters_.empty()) {
284     readers_ += read_waiters_.size();
285
286     while (!read_waiters_.empty()) {
287       MutexWaiter& to_wake = read_waiters_.front();
288       read_waiters_.pop_front();
289       to_wake.baton.post();
290     }
291   }
292   pthread_spin_unlock(&lock_);
293 }
294
295 }}