1 /* Copyright (C) 2014 InfiniDB, Inc.
2 
3    This program is free software; you can redistribute it and/or
4    modify it under the terms of the GNU General Public License
5    as published by the Free Software Foundation; version 2 of
6    the License.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License
14    along with this program; if not, write to the Free Software
15    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16    MA 02110-1301, USA. */
17 
18 /******************************************************************************
19  * $Id$
20  *
21  *****************************************************************************/
22 
23 /** @file
24  * class RWLock interface
25  */
26 
27 #ifndef RWLOCK_H_
28 #define RWLOCK_H_
29 
30 #include <unistd.h>
31 #include <stdexcept>
32 
33 #include <boost/interprocess/shared_memory_object.hpp>
34 #include <boost/interprocess/mapped_region.hpp>
35 #include <boost/interprocess/sync/interprocess_semaphore.hpp>
36 #include <boost/date_time/posix_time/posix_time.hpp>
37 
38 #if defined(_MSC_VER) && defined(xxxRWLOCK_DLLEXPORT)
39 #define EXPORT __declspec(dllexport)
40 #else
41 #define EXPORT
42 #endif
43 
44 namespace rwlock
45 {
46 
47 /// the layout of the shmseg
48 struct State
49 {
50 #ifdef _MSC_VER
51     volatile LONG writerswaiting;
52     volatile LONG writing;
53     volatile LONG readerswaiting;
54     volatile LONG reading;
55 #else
56     volatile int writerswaiting;
57     volatile int writing;
58     volatile int readerswaiting;
59     volatile int reading;
60 #endif
61     boost::interprocess::interprocess_semaphore sems[3];
62 };
63 
64 /* the lock state without the semaphores, passed out by timed_write_lock() for
65 class RWLockMonitor
66 */
67 struct LockState
68 {
69 #ifdef _MSC_VER
70     LONG writerswaiting;
71     LONG writing;
72     LONG readerswaiting;
73     LONG reading;
74     bool mutexLocked;
75 #else
76     int writerswaiting;
77     int writing;
78     int readerswaiting;
79     int reading;
80     bool mutexLocked;
81 #endif
82 };
83 
84 class RWLockShmImpl
85 {
86 public:
87     static RWLockShmImpl* makeRWLockShmImpl(int key, bool* excl = 0);
88 
89     boost::interprocess::shared_memory_object fStateShm;
90     boost::interprocess::mapped_region fRegion;
91     State* fState;
92 
keyString()93     std::string keyString()
94     {
95         return fKeyString;
96     }
97 private:
98     explicit RWLockShmImpl(int key, bool excl = false);
99     ~RWLockShmImpl();
100     RWLockShmImpl(const RWLockShmImpl& rhs);
101     RWLockShmImpl& operator=(const RWLockShmImpl& rhs);
102     std::string fKeyString;
103 };
104 
105 class not_excl : public std::exception
106 {
107 public:
what()108     virtual const char* what() const throw()
109     {
110         return "not_excl";
111     }
112 };
113 
114 class wouldblock : public std::exception
115 {
116 public:
what()117     virtual const char* what() const throw()
118     {
119         return "wouldblock";
120     }
121 };
122 
123 /** @brief Implements RW locks for use across threads & processes
124  *
125  * Implements RW locks for use across threads & processes.  Every
126  * instance that shares a lock must be instantiated using the same
127  * key.  There is 'no limit' on the number of RW locks that can
128  * exist on the system at any one time.
129  *
130  * Summary of operation:
131  * 		- readers can work concurrently
132  *		- writers get exclusive access
133  * 		- writers have priority
134  *		- all state persists across all invocations sharing a given key
135  *
136  * Note: because state has to persist, it will have to be cleaned
137  * up somewhere else.  Crashes while holding a read or write lock will
138  * eventually deadlock the set of processes that share the same key obviously.
139  */
140 class RWLock
141 {
142 public:
143 
144     // semaphore numbers
145     static const int MUTEX = 0;
146     static const int READERS = 1;
147     static const int WRITERS = 2;
148 
149     /** @brief Keyed constructor.
150      *
151      * Instantiate an RWLock with the given key.  All instances that
152      * share a key share the same lock.
153      *
154      * @param key The key
155      * @param excl If true and this is the first instance with the
156      * supplied key, it will return holding the write lock.  If true and
157      * this is not the first instance, it will throw not_excl.  The intent
158      * is similar to the IPC_EXCL flag in the sem/shm implementations.
159      */
160     EXPORT explicit RWLock(int key, bool* excl = 0);
161 
162     EXPORT ~RWLock();
163 
164     /** @brief Grab a read lock
165      *
166      * Grab a read lock.  This will block iff writers are waiting or
167      * a writer is active. The version with priority ignores any
168      * waiting threads and grabs the lock.
169      *
170      * @param block (For testing only) If false, will throw
171      * wouldblock instead of blocking
172      */
173     EXPORT void read_lock(bool block = true);
174 
175     EXPORT void read_lock_priority(bool block = true);
176 
177     /** @brief Release a read lock.
178      *
179      * Release a read lock.
180      */
181     EXPORT void read_unlock();
182 
183     /** @brief Grab a write lock
184      *
185      * Grab a write lock.  This will block while another writer or reader is
186      * active and will have exclusive access on waking.
187      *
188      * @param block (For testing only) If false, will throw
189      * wouldblock instead of blocking
190      */
191     EXPORT void write_lock(bool block = true);
192 
193     /** @brief A timed write lock.
194      *
195      * Queues up for the write lock for a specified amount of time.  Returns
196      * true if it got the lock, return false if it timed out first.
197      * If the timeout happens, it will also return the lock state if passed
198      * a non-NULL LockState struct.  This is a specialization for supporting
199      * the RWLockMonitor class.
200      */
201     EXPORT bool timed_write_lock(const struct timespec& ts,
202                                  struct LockState* state = 0);
203 
204     /** @brief Release a write lock.
205      *
206      * Release a write lock.
207      */
208     EXPORT void write_unlock();
209 
210     /* note: these haven't been proven yet */
211 
212     /** @brief Upgrade a read lock to a write lock
213      *
214      * Upgrade a read lock to a write lock.  It may have to block
215      * if there are other readers currently reading.  No guarantees of atomicity.
216      */
217     EXPORT void upgrade_to_write();
218 
219     /** @brief Downgrade a write lock to a read lock
220      *
221      * Downgrade a write lock to a read lock.  The conversion happens
222      * atomically.
223      */
224     EXPORT void downgrade_to_read();
225 
226     /** @brief Reset the lock's state (Use with caution!)
227      *
228      * If the lock gets into a bad state in testing or something,
229      * this will reset the state.
230      * @warning This is safe only if there are no other threads using this
231      * lock.
232      */
233     EXPORT void reset();
234 
235     /* These are for white box testing only */
lock()236     inline void lock()
237     {
238         down(MUTEX, true);
239     }
unlock()240     inline void unlock()
241     {
242         up(MUTEX);
243     }
getWriting()244     inline int getWriting() const
245     {
246         return fPImpl->fState->writing;
247     }
getReading()248     inline int getReading() const
249     {
250         return fPImpl->fState->reading;
251     }
getWritersWaiting()252     inline int getWritersWaiting() const
253     {
254         return fPImpl->fState->writerswaiting;
255     }
getReadersWaiting()256     inline int getReadersWaiting() const
257     {
258         return fPImpl->fState->readerswaiting;
259     }
260     LockState getLockState();
261 
262 private:
263     RWLock(const RWLock& rwl);
264     RWLock& operator=(const RWLock& rwl);
265 
getSemval(int)266     inline int getSemval(int) const
267     {
268         return 0;
269     }
270     void down(int num, bool block = true);
271     bool timed_down(int num, const boost::posix_time::ptime& ts);  // to support timed_write_lock()
272     void up(int num);
273 
274     RWLockShmImpl* fPImpl;
275 };
276 
277 } //namespace rwlock
278 
279 #undef EXPORT
280 
281 #endif
282 // vim:ts=4 sw=4:
283