blocxx
GenericRWLockImpl.hpp
Go to the documentation of this file.
1 /*******************************************************************************
2 * Copyright (C) 2007, Quest Software All rights reserved.
3 * Copyright (C) 2006, Novell, Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of
14 * Quest Software,
15 * nor Novell, Inc.,
16 * nor the names of its contributors or employees may be used to
17 * endorse or promote products derived from this software without
18 * specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 *******************************************************************************/
32 
33 
39 #ifndef BLOCXX_GENERIC_RWLOCK_IMPL_HPP_INCLUDE_GUARD_
40 #define BLOCXX_GENERIC_RWLOCK_IMPL_HPP_INCLUDE_GUARD_
41 #include "blocxx/BLOCXX_config.h"
42 #include "blocxx/CommonFwd.hpp"
44 #include "blocxx/Condition.hpp"
45 #include "blocxx/Exception.hpp"
47 #include "blocxx/ExceptionIds.hpp"
48 #include "blocxx/Timeout.hpp"
49 #include "blocxx/TimeoutTimer.hpp"
50 #include "blocxx/Assertion.hpp"
51 
52 #include <map>
53 
54 namespace BLOCXX_NAMESPACE
55 {
56 
63 template <typename IdT, typename CompareT>
65 {
66 public:
69 
73  void acquireReadLock(const IdT id, const Timeout& timeout);
74 
81  void acquireWriteLock(const IdT id, const Timeout& timeout);
82 
86  void releaseReadLock(const IdT id);
87 
91  void releaseWriteLock(const IdT id);
92 
93 private:
94 
96 
97  bool m_canRead;
99 
101  unsigned m_numReaders;
102  unsigned m_numWriters; // current writer + upgrading writer
103 
104  struct LockerInfo
105  {
106  unsigned int readCount;
107  unsigned int writeCount;
108 
109  bool isReader() const
110  {
111  return readCount > 0;
112  }
113 
114  bool isWriter() const
115  {
116  return writeCount > 0;
117  }
118  };
119 
120  typedef std::map<IdT, LockerInfo, CompareT> IdMap;
122 
123  // unimplemented
126 };
127 
129 template <typename IdT, typename CompareT>
131  : m_canRead(true)
132  , m_numReaders(0)
133  , m_numWriters(0)
134 {
135 }
137 template <typename IdT, typename CompareT>
139 {
140 }
142 template <typename IdT, typename CompareT>
143 void
145 {
146  TimeoutTimer timer(timeout);
147 
149  typename IdMap::iterator info = m_lockerInfo.find(id);
150 
151  if (info != m_lockerInfo.end())
152  {
153  LockerInfo& ti(info->second);
154  // id already have a read or write lock, so just increment.
155  BLOCXX_ASSERT(ti.isReader() || ti.isWriter());
156  ++ti.readCount;
157  return;
158  }
159 
160  // id is a new reader
161  while (!m_canRead || m_numWriters > 0)
162  {
163  if (!m_waiting_readers.timedWait(l, timer.asAbsoluteTimeout()))
164  {
165  BLOCXX_THROW(TimeoutException, "Timeout while waiting for read lock.");
166  }
167  }
168 
169  // Increase the reader count
170  LockerInfo lockerInfo;
171  lockerInfo.readCount = 1;
172  lockerInfo.writeCount = 0;
173  m_lockerInfo.insert(typename IdMap::value_type(id, lockerInfo));
174 
175  ++m_numReaders;
176 }
177 
179 template <typename IdT, typename CompareT>
180 void
182 {
184 
185  typename IdMap::iterator pInfo = m_lockerInfo.find(id);
186 
187  if (pInfo == m_lockerInfo.end() || !pInfo->second.isReader())
188  {
189  BLOCXX_THROW(GenericRWLockImplException, "Cannot release a read lock when no read lock is held");
190  }
191 
192  LockerInfo& info(pInfo->second);
193  --info.readCount;
194 
195  if (!info.isWriter() && !info.isReader())
196  {
197  --m_numReaders;
198  if (m_numReaders == 0)
199  {
200  // This needs to wake them all up. In the case where one thread is waiting to upgrade a read to a write lock
201  // and others are waiting to get a write lock, we have to wake up the thread trying to upgrade.
202  m_waiting_writers.notifyAll();
203  }
204  m_lockerInfo.erase(pInfo);
205  }
206 }
207 
209 template <typename IdT, typename CompareT>
210 void
212 {
213  // 7 cases:
214  // 1. No id has the lock
215  // Get the lock
216  // 2. This id has the write lock
217  // Increment the lock count
218  // 3. Another id has the write lock & other ids may be waiting for read and/or write locks.
219  // Block until the lock is acquired.
220  // 4. Only this id has a read lock
221  // Increment the write lock count .
222  // 5. >0 other ids have the read lock & other ids may be waiting for write locks.
223  // Block until the write lock is acquired.
224  // 6. This id and other ids have the read lock
225  // Block new readers and writers and wait until existing readers finish.
226  // 7. This id and other ids have the read lock and one of the other ids has requested a write lock.
227  // Throw an exception.
228 
229  TimeoutTimer timer(timeout);
230 
232 
233  typename IdMap::iterator pInfo = m_lockerInfo.find(id);
234  if (pInfo != m_lockerInfo.end())
235  {
236  // This id already has some sort of lock
237  LockerInfo& ti(pInfo->second);
238  BLOCXX_ASSERT(ti.isReader() || ti.isWriter());
239 
240  if (!ti.isWriter())
241  {
242  // The id is upgrading
243 
244  BLOCXX_ASSERT(m_numWriters == 0 || m_numWriters == 1);
245  if (m_numWriters == 1)
246  {
247  // another id beat us to upgrading the write lock. Throw an exception.
248  BLOCXX_THROW(DeadlockException, "Upgrading read lock to a write lock failed, another upgrade is already in progress.");
249  }
250 
251  // switch from being a reader to a writer
252  --m_numReaders;
253  // mark us as a writer, this will prevent other ids from becoming a writer
254  ++m_numWriters;
255 
256  // This thread isn't the only reader. Wait for others to finish.
257  while (m_numReaders != 0)
258  {
259  // stop new readers - inside while loop, because it may get reset by other ids releasing locks.
260  m_canRead = false;
261 
262  if (!m_waiting_writers.timedWait(l, timer.asAbsoluteTimeout()))
263  {
264  // undo changes
265  ++m_numReaders;
266  --m_numWriters;
267  m_canRead = true;
268  if (m_numWriters == 0)
269  {
270  m_waiting_readers.notifyAll();
271  }
272  BLOCXX_THROW(TimeoutException, "Timeout while waiting for write lock.");
273  }
274  }
275  }
276  ++ti.writeCount;
277 
278  }
279  else
280  {
281  // This id doesn't have any lock
282 
283  while (m_numReaders != 0 || m_numWriters != 0)
284  {
285  // stop new readers
286  m_canRead = false;
287 
288  if (!m_waiting_writers.timedWait(l, timer.asAbsoluteTimeout()))
289  {
290  m_canRead = true;
291  if (m_numWriters == 0)
292  {
293  m_waiting_readers.notifyAll();
294  }
295  BLOCXX_THROW(TimeoutException, "Timeout while waiting for write lock.");
296  }
297  }
298 
299  LockerInfo ti;
300  ti.readCount = 0;
301  ti.writeCount = 1;
302  m_lockerInfo.insert(typename IdMap::value_type(id, ti));
303  ++m_numWriters;
304  m_canRead = false;
305  }
306 
307 }
308 
310 template <typename IdT, typename CompareT>
311 void
313 {
315 
316  typename IdMap::iterator pInfo = m_lockerInfo.find(id);
317 
318  if (pInfo == m_lockerInfo.end() || !pInfo->second.isWriter())
319  {
320  BLOCXX_THROW(GenericRWLockImplException, "Cannot release a write lock when no write lock is held");
321  }
322 
323  LockerInfo& ti(pInfo->second);
324 
325  BLOCXX_ASSERT(ti.isWriter());
326 
327  --ti.writeCount;
328 
329  if (!ti.isWriter())
330  {
331  --m_numWriters;
332 
333  BLOCXX_ASSERT(m_numWriters == 0);
334 
335  m_canRead = true;
336  if (ti.isReader())
337  {
338  // restore reader status
339  ++m_numReaders;
340  }
341  else
342  {
343  // This id no longer holds locks.
344  m_waiting_writers.notifyOne();
345  m_lockerInfo.erase(pInfo);
346  }
347  m_waiting_readers.notifyAll();
348  }
349 }
350 
351 
352 } // end namespace BLOCXX_NAMESPACE
353 
354 #endif