adding Promise::await
[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 namespace folly {
19 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(
117       (state_ == State::UNLOCKED && readers_ == 0) ||
118       (state_ == State::READ_LOCKED && readers_ > 0));
119   assert(read_waiters_.empty());
120   state_ = State::READ_LOCKED;
121   readers_ += 1;
122   pthread_spin_unlock(&lock_);
123 }
124
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) {
131     MutexWaiter waiter;
132     read_waiters_.push_back(waiter);
133     pthread_spin_unlock(&lock_);
134
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_);
145         return false;
146       }
147       pthread_spin_unlock(&lock_);
148     }
149     return true;
150   }
151   assert(
152       (state_ == State::UNLOCKED && readers_ == 0) ||
153       (state_ == State::READ_LOCKED && readers_ > 0));
154   assert(read_waiters_.empty());
155   state_ = State::READ_LOCKED;
156   readers_ += 1;
157   pthread_spin_unlock(&lock_);
158   return true;
159 }
160
161 template <typename BatonType>
162 bool TimedRWMutex<BatonType>::try_read_lock() {
163   pthread_spin_lock(&lock_);
164   if (state_ != State::WRITE_LOCKED) {
165     assert(
166         (state_ == State::UNLOCKED && readers_ == 0) ||
167         (state_ == State::READ_LOCKED && readers_ > 0));
168     assert(read_waiters_.empty());
169     state_ = State::READ_LOCKED;
170     readers_ += 1;
171     pthread_spin_unlock(&lock_);
172     return true;
173   }
174   pthread_spin_unlock(&lock_);
175   return false;
176 }
177
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_);
185     return;
186   }
187   MutexWaiter waiter;
188   write_waiters_.push_back(waiter);
189   pthread_spin_unlock(&lock_);
190   waiter.baton.wait();
191 }
192
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_);
202     return true;
203   }
204   MutexWaiter waiter;
205   write_waiters_.push_back(waiter);
206   pthread_spin_unlock(&lock_);
207
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_);
218       return false;
219     }
220     pthread_spin_unlock(&lock_);
221   }
222   assert(state_ == State::WRITE_LOCKED);
223   return true;
224 }
225
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_);
233     return true;
234   }
235   pthread_spin_unlock(&lock_);
236   return false;
237 }
238
239 template <typename BatonType>
240 void TimedRWMutex<BatonType>::unlock() {
241   pthread_spin_lock(&lock_);
242   assert(state_ != State::UNLOCKED);
243   assert(
244       (state_ == State::READ_LOCKED && readers_ > 0) ||
245       (state_ == State::WRITE_LOCKED && readers_ == 0));
246   if (state_ == State::READ_LOCKED) {
247     readers_ -= 1;
248   }
249
250   if (!read_waiters_.empty()) {
251     assert(
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();
256
257     while (!read_waiters_.empty()) {
258       MutexWaiter& to_wake = read_waiters_.front();
259       read_waiters_.pop_front();
260       to_wake.baton.post();
261     }
262   } else if (readers_ == 0) {
263     if (!write_waiters_.empty()) {
264       assert(read_waiters_.empty());
265       state_ = State::WRITE_LOCKED;
266
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();
271     } else {
272       verify_unlocked_properties();
273       state_ = State::UNLOCKED;
274     }
275   } else {
276     assert(state_ == State::READ_LOCKED);
277   }
278   pthread_spin_unlock(&lock_);
279 }
280
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;
286   readers_ += 1;
287
288   if (!read_waiters_.empty()) {
289     readers_ += read_waiters_.size();
290
291     while (!read_waiters_.empty()) {
292       MutexWaiter& to_wake = read_waiters_.front();
293       read_waiters_.pop_front();
294       to_wake.baton.post();
295     }
296   }
297   pthread_spin_unlock(&lock_);
298 }
299 }
300 }