Move fibers out of experimental
[folly.git] / folly / fibers / TimedMutex.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 #include <pthread.h>
19
20 #include <folly/fibers/GenericBaton.h>
21
22 namespace folly {
23 namespace fibers {
24
25 /**
26  * @class TimedMutex
27  *
28  * Like mutex but allows timed_lock in addition to lock and try_lock.
29  **/
30 template <typename BatonType>
31 class TimedMutex {
32  public:
33   TimedMutex() {
34     pthread_spin_init(&lock_, PTHREAD_PROCESS_PRIVATE);
35   }
36
37   ~TimedMutex() {
38     pthread_spin_destroy(&lock_);
39   }
40
41   TimedMutex(const TimedMutex& rhs) = delete;
42   TimedMutex& operator=(const TimedMutex& rhs) = delete;
43   TimedMutex(TimedMutex&& rhs) = delete;
44   TimedMutex& operator=(TimedMutex&& rhs) = delete;
45
46   // Lock the mutex. The thread / fiber is blocked until the mutex is free
47   void lock();
48
49   // Lock the mutex. The thread / fiber will be blocked for a time duration.
50   //
51   // @return        true if the mutex was locked, false otherwise
52   template <typename Rep, typename Period>
53   bool timed_lock(const std::chrono::duration<Rep, Period>& duration);
54
55   // Try to obtain lock without blocking the thread or fiber
56   bool try_lock();
57
58   // Unlock the mutex and wake up a waiter if there is one
59   void unlock();
60
61  private:
62   typedef boost::intrusive::list_member_hook<> MutexWaiterHookType;
63
64   // represents a waiter waiting for the lock. The waiter waits on the
65   // baton until it is woken up by a post or timeout expires.
66   struct MutexWaiter {
67     BatonType baton;
68     MutexWaiterHookType hook;
69   };
70
71   typedef boost::intrusive::
72       member_hook<MutexWaiter, MutexWaiterHookType, &MutexWaiter::hook>
73           MutexWaiterHook;
74
75   typedef boost::intrusive::list<
76       MutexWaiter,
77       MutexWaiterHook,
78       boost::intrusive::constant_time_size<true>>
79       MutexWaiterList;
80
81   pthread_spinlock_t lock_; //< lock to protect waiter list
82   bool locked_ = false; //< is this locked by some thread?
83   MutexWaiterList waiters_; //< list of waiters
84 };
85
86 /**
87  * @class TimedRWMutex
88  *
89  * A readers-writer lock which allows multiple readers to hold the
90  * lock simultaneously or only one writer.
91  *
92  * NOTE: This is a reader-preferred RWLock i.e. readers are give priority
93  * when there are both readers and writers waiting to get the lock.
94  **/
95 template <typename BatonType>
96 class TimedRWMutex {
97  public:
98   TimedRWMutex() {
99     pthread_spin_init(&lock_, PTHREAD_PROCESS_PRIVATE);
100   }
101
102   ~TimedRWMutex() {
103     pthread_spin_destroy(&lock_);
104   }
105
106   TimedRWMutex(const TimedRWMutex& rhs) = delete;
107   TimedRWMutex& operator=(const TimedRWMutex& rhs) = delete;
108   TimedRWMutex(TimedRWMutex&& rhs) = delete;
109   TimedRWMutex& operator=(TimedRWMutex&& rhs) = delete;
110
111   // Lock for shared access. The thread / fiber is blocked until the lock
112   // can be acquired.
113   void read_lock();
114
115   // Like read_lock except the thread /fiber is blocked for a time duration
116   // @return        true if locked successfully, false otherwise.
117   template <typename Rep, typename Period>
118   bool timed_read_lock(const std::chrono::duration<Rep, Period>& duration);
119
120   // Like read_lock but doesn't block the thread / fiber if the lock can't
121   // be acquired.
122   // @return        true if lock was acquired, false otherwise.
123   bool try_read_lock();
124
125   // Obtain an exclusive lock. The thread / fiber is blocked until the lock
126   // is available.
127   void write_lock();
128
129   // Like write_lock except the thread / fiber is blocked for a time duration
130   // @return        true if locked successfully, false otherwise.
131   template <typename Rep, typename Period>
132   bool timed_write_lock(const std::chrono::duration<Rep, Period>& duration);
133
134   // Like write_lock but doesn't block the thread / fiber if the lock cant be
135   // obtained.
136   // @return        true if lock was acquired, false otherwise.
137   bool try_write_lock();
138
139   // Wrapper for write_lock() for compatibility with Mutex
140   void lock() {
141     write_lock();
142   }
143
144   // Realease the lock. The thread / fiber will wake up all readers if there are
145   // any. If there are waiting writers then only one of them will be woken up.
146   // NOTE: readers are given priority over writers (see above comment)
147   void unlock();
148
149   // Downgrade the lock. The thread / fiber will wake up all readers if there
150   // are any.
151   void downgrade();
152
153   class ReadHolder {
154    public:
155     explicit ReadHolder(TimedRWMutex& lock) : lock_(&lock) {
156       lock_->read_lock();
157     }
158
159     ~ReadHolder() {
160       if (lock_) {
161         lock_->unlock();
162       }
163     }
164
165     ReadHolder(const ReadHolder& rhs) = delete;
166     ReadHolder& operator=(const ReadHolder& rhs) = delete;
167     ReadHolder(ReadHolder&& rhs) = delete;
168     ReadHolder& operator=(ReadHolder&& rhs) = delete;
169
170    private:
171     TimedRWMutex* lock_;
172   };
173
174   class WriteHolder {
175    public:
176     explicit WriteHolder(TimedRWMutex& lock) : lock_(&lock) {
177       lock_->write_lock();
178     }
179
180     ~WriteHolder() {
181       if (lock_) {
182         lock_->unlock();
183       }
184     }
185
186     WriteHolder(const WriteHolder& rhs) = delete;
187     WriteHolder& operator=(const WriteHolder& rhs) = delete;
188     WriteHolder(WriteHolder&& rhs) = delete;
189     WriteHolder& operator=(WriteHolder&& rhs) = delete;
190
191    private:
192     TimedRWMutex* lock_;
193   };
194
195  private:
196   // invariants that must hold when the lock is not held by anyone
197   void verify_unlocked_properties() {
198     assert(readers_ == 0);
199     assert(read_waiters_.empty());
200     assert(write_waiters_.empty());
201   }
202
203   // Different states the lock can be in
204   enum class State {
205     UNLOCKED,
206     READ_LOCKED,
207     WRITE_LOCKED,
208   };
209
210   typedef boost::intrusive::list_member_hook<> MutexWaiterHookType;
211
212   // represents a waiter waiting for the lock.
213   struct MutexWaiter {
214     BatonType baton;
215     MutexWaiterHookType hook;
216   };
217
218   typedef boost::intrusive::
219       member_hook<MutexWaiter, MutexWaiterHookType, &MutexWaiter::hook>
220           MutexWaiterHook;
221
222   typedef boost::intrusive::list<
223       MutexWaiter,
224       MutexWaiterHook,
225       boost::intrusive::constant_time_size<true>>
226       MutexWaiterList;
227
228   pthread_spinlock_t lock_; //< lock protecting the internal state
229   // (state_, read_waiters_, etc.)
230   State state_ = State::UNLOCKED;
231
232   uint32_t readers_ = 0; //< Number of readers who have the lock
233
234   MutexWaiterList write_waiters_; //< List of thread / fibers waiting for
235   //  exclusive access
236
237   MutexWaiterList read_waiters_; //< List of thread / fibers waiting for
238   //  shared access
239 };
240 }
241 }
242
243 #include "TimedMutex-inl.h"