VirtualBox

source: vbox/trunk/src/VBox/Main/include/AutoLock.h@ 4041

Last change on this file since 4041 was 3329, checked in by vboxsync, 17 years ago

Main: Added AutoLock::maybeRlock()/maybeWlock() in order for conditional locking in AutoMultiLock (lockable objects may be NULL in which case the lock will be a no-op).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.7 KB
Line 
1/** @file
2 *
3 * AutoLock: smart critical section wrapper
4 */
5
6/*
7 * Copyright (C) 2006-2007 innotek GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * If you received this file as part of a commercial VirtualBox
18 * distribution, then only the terms of your commercial VirtualBox
19 * license agreement apply instead of the previous paragraph.
20 */
21
22#ifndef ____H_AUTOLOCK
23#define ____H_AUTOLOCK
24
25#include <iprt/cdefs.h>
26#include <iprt/types.h>
27#include <iprt/critsect.h>
28#include <iprt/thread.h>
29
30#if defined(DEBUG)
31# include <iprt/asm.h> // for ASMReturnAddress
32#endif
33
34namespace util
35{
36
37template <size_t> class AutoMultiLock;
38namespace internal { struct LockableTag; }
39
40/**
41 * Smart class to safely manage critical sections. Also provides ecplicit
42 * lock, unlock leave and enter operations.
43 *
44 * When constructing an instance, it enters the given critical
45 * section. This critical section will be exited automatically when the
46 * instance goes out of scope (i.e. gets destroyed).
47 */
48class AutoLock
49{
50public:
51
52 #if defined(DEBUG)
53 # define ___CritSectEnter(cs) \
54 RTCritSectEnterDebug ((cs), \
55 "AutoLock::lock()/enter() return address >>>", 0, \
56 (RTUINTPTR) ASMReturnAddress())
57 #else
58 # define ___CritSectEnter(cs) RTCritSectEnter ((cs))
59 #endif
60
61 /**
62 * Lock (critical section) handle. An auxiliary base class for structures
63 * that need locking. Instances of classes inherited from it can be passed
64 * as arguments to the AutoLock constructor.
65 */
66 class Handle
67 {
68 public:
69
70 Handle() { RTCritSectInit (&mCritSect); }
71 virtual ~Handle() { RTCritSectDelete (&mCritSect); }
72
73 /** Returns |true| if this handle is locked on the current thread. */
74 bool isLockedOnCurrentThread() const
75 {
76 return RTCritSectIsOwner (&mCritSect);
77 }
78
79 /** Returns a tag to lock this handle for reading by AutoMultiLock */
80 internal::LockableTag rlock() const;
81 /** Returns a tag to lock this handle for writing by AutoMultiLock */
82 internal::LockableTag wlock() const;
83
84 private:
85
86 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (Handle)
87
88 mutable RTCRITSECT mCritSect;
89
90 friend class AutoLock;
91 template <size_t> friend class AutoMultiLock;
92 };
93
94 /**
95 * Lockable interface. An abstract base for classes that need locking.
96 * Unlike Handle that makes the lock handle a part of class data, this
97 * class allows subclasses to decide which lock handle to use.
98 */
99 class Lockable
100 {
101 public:
102
103 /**
104 * Returns a pointer to a Handle used by AutoLock for locking.
105 * Subclasses are allowed to return |NULL| -- in this case,
106 * the AutoLock object constructed using an instance of such
107 * subclass will simply turn into no-op.
108 */
109 virtual Handle *lockHandle() const = 0;
110
111 /**
112 * Equivalent to |#lockHandle()->isLockedOnCurrentThread()|.
113 * Returns |false| if lockHandle() returns NULL.
114 */
115 bool isLockedOnCurrentThread()
116 {
117 Handle *h = lockHandle();
118 return h ? h->isLockedOnCurrentThread() : false;
119 }
120
121 /**
122 * Returns a tag to lock this handle for reading by AutoMultiLock.
123 * Shortcut to |lockHandle()->rlock()|.
124 * The returned tag is a no-op, when lockHandle() returns |NULL|.
125 */
126 internal::LockableTag rlock() const;
127
128 /**
129 * Returns a tag to lock this handle for writing by AutoMultiLock.
130 * Shortcut to |lockHandle()->wlock()|.
131 * The returned tag is a no-op, when lockHandle() returns |NULL|.
132 */
133 internal::LockableTag wlock() const;
134 };
135
136 AutoLock() : mCritSect (NULL), mLevel (0), mLeftLevel (0) {}
137
138 AutoLock (RTCRITSECT &aCritSect)
139 : mCritSect (&aCritSect), mLevel (0), mLeftLevel (0) { lock(); }
140
141 AutoLock (RTCRITSECT *aCritSect)
142 : mCritSect (aCritSect), mLevel (0), mLeftLevel (0) { lock(); }
143
144 AutoLock (const Handle &aHandle)
145 : mCritSect (&aHandle.mCritSect), mLevel (0), mLeftLevel (0) { lock(); }
146
147 AutoLock (const Handle *aHandle)
148 : mCritSect (aHandle ? &aHandle->mCritSect : NULL)
149 , mLevel (0), mLeftLevel (0) { lock(); }
150
151 AutoLock (const Lockable &aLockable)
152 : mCritSect (critSect (&aLockable))
153 , mLevel (0), mLeftLevel (0) { lock(); }
154
155 AutoLock (const Lockable *aLockable)
156 : mCritSect (aLockable ? critSect (aLockable) : NULL)
157 , mLevel (0), mLeftLevel (0) { lock(); }
158
159 ~AutoLock()
160 {
161 if (mCritSect)
162 {
163 if (mLeftLevel)
164 {
165 mLeftLevel -= mLevel;
166 mLevel = 0;
167 for (; mLeftLevel; -- mLeftLevel)
168 RTCritSectEnter (mCritSect);
169 }
170 AssertMsg (mLevel <= 1, ("Lock level > 1: %d\n", mLevel));
171 for (; mLevel; -- mLevel)
172 RTCritSectLeave (mCritSect);
173 }
174 }
175
176 /**
177 * Tries to acquire the lock or increases the lock level
178 * if the lock is already owned by this thread.
179 */
180 void lock()
181 {
182 if (mCritSect)
183 {
184 AssertMsgReturn (mLeftLevel == 0, ("lock() after leave()\n"), (void) 0);
185 ___CritSectEnter (mCritSect);
186 ++ mLevel;
187 }
188 }
189
190 /**
191 * Decreases the lock level. If the level goes to zero, the lock
192 * is released by the current thread.
193 */
194 void unlock()
195 {
196 if (mCritSect)
197 {
198 AssertMsgReturn (mLevel > 0, ("Lock level is zero\n"), (void) 0);
199 AssertMsgReturn (mLeftLevel == 0, ("lock() after leave()\n"), (void) 0);
200 -- mLevel;
201 RTCritSectLeave (mCritSect);
202 }
203 }
204
205 /**
206 * Causes the current thread to completely release the lock
207 * (including locks acquired by all other instances of this class
208 * referring to the same object or handle). #enter() must be called
209 * to acquire the lock back and restore all lock levels.
210 */
211 void leave()
212 {
213 if (mCritSect)
214 {
215 AssertMsg (mLevel > 0, ("Lock level is zero\n"));
216 AssertMsgReturn (mLeftLevel == 0, ("leave() w/o enter()\n"), (void) 0);
217 mLeftLevel = RTCritSectGetRecursion (mCritSect);
218 for (uint32_t left = mLeftLevel; left; -- left)
219 RTCritSectLeave (mCritSect);
220 Assert (mLeftLevel >= mLevel);
221 }
222 }
223
224 /**
225 * Causes the current thread to acquire the lock again and restore
226 * all lock levels after calling #leave().
227 */
228 void enter()
229 {
230 if (mCritSect)
231 {
232 AssertMsg (mLevel > 0, ("Lock level is zero\n"));
233 AssertMsgReturn (mLeftLevel > 0, ("enter() w/o leave()\n"), (void) 0);
234 for (; mLeftLevel; -- mLeftLevel)
235 ___CritSectEnter (mCritSect);
236 }
237 }
238
239 /**
240 * Current level of the nested lock. 1 means the lock is not currently
241 * nested.
242 */
243 uint32_t level() const { return RTCritSectGetRecursion (mCritSect); }
244
245 bool isNull() const { return mCritSect == NULL; }
246 bool operator !() const { return isNull(); }
247
248 /** Returns |true| if this instance manages the given lock handle. */
249 bool belongsTo (const Handle &aHandle)
250 {
251 return &aHandle.mCritSect == mCritSect;
252 }
253
254 /** Returns |true| if this instance manages the given lock handle. */
255 bool belongsTo (const Handle *aHandle)
256 {
257 return aHandle && &aHandle->mCritSect == mCritSect;
258 }
259
260 /** Returns |true| if this instance manages the given lockable object. */
261 bool belongsTo (const Lockable &aLockable)
262 {
263 return belongsTo (aLockable.lockHandle());
264 }
265
266 /** Returns |true| if this instance manages the given lockable object. */
267 bool belongsTo (const Lockable *aLockable)
268 {
269 return aLockable && belongsTo (aLockable->lockHandle());
270 }
271
272 /**
273 * Returns a tag to lock the given Lockable for reading by AutoMultiLock.
274 * Shortcut to |aL->lockHandle()->rlock()|.
275 * The returned tag is a no-op when @a aL is |NULL|.
276 */
277 static internal::LockableTag maybeRlock (Lockable *aL);
278
279 /**
280 * Returns a tag to lock the given Lockable for writing by AutoMultiLock.
281 * Shortcut to |aL->lockHandle()->wlock()|.
282 * The returned tag is a no-op when @a aL is |NULL|.
283 */
284 static internal::LockableTag maybeWlock (Lockable *aL);
285
286private:
287
288 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (AutoLock)
289 DECLARE_CLS_NEW_DELETE_NOOP (AutoLock)
290
291 inline static RTCRITSECT *critSect (const Lockable *l)
292 {
293 Assert (l);
294 Handle *h = l->lockHandle();
295 return h ? &h->mCritSect : NULL;
296 }
297
298 RTCRITSECT *mCritSect;
299 uint32_t mLevel;
300 uint32_t mLeftLevel;
301
302 #undef ___CritSectEnter
303};
304
305/**
306 * Prototype. Later will be used to acquire a read-only lock
307 * (read-only locks differ from regular (write) locks so that more than one
308 * read lock can be acquired simultaneously provided that there are no
309 * active write locks).
310 * @todo Implement it!
311 */
312class AutoReaderLock : public AutoLock
313{
314public:
315
316 AutoReaderLock (const Handle &aHandle) : AutoLock (aHandle) {}
317 AutoReaderLock (const Handle *aHandle) : AutoLock (aHandle) {}
318
319 AutoReaderLock (const Lockable &aLockable) : AutoLock (aLockable) {}
320 AutoReaderLock (const Lockable *aLockable) : AutoLock (aLockable) {}
321
322private:
323
324 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (AutoReaderLock)
325 DECLARE_CLS_NEW_DELETE_NOOP (AutoReaderLock)
326};
327
328namespace internal
329{
330 /**
331 * @internal
332 * Special struct to differentiate between read and write locks
333 * in AutoMultiLock constructors.
334 */
335 struct LockableTag
336 {
337 LockableTag (const AutoLock::Handle *h, char m)
338 : handle (h), mode (m) {}
339 const AutoLock::Handle * const handle;
340 const char mode;
341 };
342}
343
344inline internal::LockableTag AutoLock::Handle::rlock() const
345{
346 return internal::LockableTag (this, 'r');
347}
348
349inline internal::LockableTag AutoLock::Handle::wlock() const
350{
351 return internal::LockableTag (this, 'w');
352}
353
354inline internal::LockableTag AutoLock::Lockable::rlock() const
355{
356 return internal::LockableTag (lockHandle(), 'r');
357}
358
359inline internal::LockableTag AutoLock::Lockable::wlock() const
360{
361 return internal::LockableTag (lockHandle(), 'w');
362}
363
364/* static */
365inline internal::LockableTag AutoLock::maybeRlock (AutoLock::Lockable *aL)
366{
367 return internal::LockableTag (aL ? aL->lockHandle() : NULL, 'r');
368}
369
370/* static */
371inline internal::LockableTag AutoLock::maybeWlock (AutoLock::Lockable *aL)
372{
373 return internal::LockableTag (aL ? aL->lockHandle() : NULL, 'w');
374}
375
376/**
377 * Smart template class to safely enter and leave a list of critical sections.
378 *
379 * When constructing an instance, it enters all given critical sections
380 * (in an "atomic", "all or none" fashion). These critical sections will be
381 * exited automatically when the instance goes out of scope (i.e. gets destroyed).
382 *
383 * It is possible to lock different critical sections in two different modes:
384 * for writing (as AutoLock does) or for reading (as AutoReaderLock does).
385 * The lock mode is determined by the method called on an AutoLock::Handle or
386 * AutoLock::Lockable instance when passing it to the AutoMultiLock constructor:
387 * |rlock()| to lock for reading or |wlock()| to lock for writing.
388 *
389 * Instances of this class are constructed as follows:
390 * <code>
391 * ...
392 * AutoLock::Handle data1, data2;
393 * ...
394 * {
395 * AutoMultiLock <2> multiLock (data1.wlock(), data2.rlock());
396 * // all locks are entered here:
397 * // data1 is entered in write mode (like AutoLock)
398 * // data2 is entered in read mode (like AutoReaderLock),
399 * }
400 * // all locks are exited here
401 * </code>
402 *
403 * The number of critical sections passed to the constructor must exactly
404 * match the number specified as the @a tCnt parameter of the template.
405 *
406 * @param tCnt number of critical sections to manage
407 */
408template <size_t tCnt>
409class AutoMultiLock
410{
411public:
412
413 #if defined(DEBUG)
414 # define ___CritSectEnterMulti(n, acs) \
415 RTCritSectEnterMultipleDebug ((n), (acs), \
416 "AutoMultiLock instantiation address >>>", 0, \
417 (RTUINTPTR) ASMReturnAddress())
418 #else
419 # define ___CritSectEnterMulti(n, acs) RTCritSectEnterMultiple ((n), (acs))
420 #endif
421
422 #define A(n) internal::LockableTag l##n
423 #define B(n) if (l##n.handle) { /* skip NULL tags */ \
424 mS[i] = &l##n.handle->mCritSect; \
425 mM[i++] = l##n.mode; \
426 } else
427 #define C(n) \
428 mLocked = true; \
429 mM [0] = 0; /* for safety in case of early return */ \
430 AssertMsg (tCnt == n, \
431 ("This AutoMultiLock is for %d locks, but %d were passed!\n", tCnt, n)); \
432 if (tCnt != n) return; \
433 int i = 0
434 /// @todo (dmik) this will change when we switch to RTSemRW*
435 #define D() mM [i] = 0; /* end of array */ \
436 ___CritSectEnterMulti (strlen (mM), mS)
437
438 AutoMultiLock (A(0), A(1))
439 { C(2); B(0); B(1); D(); }
440 AutoMultiLock (A(0), A(1), A(2))
441 { C(3); B(0); B(1); B(2); D(); }
442 AutoMultiLock (A(0), A(1), A(2), A(3))
443 { C(4); B(0); B(1); B(2); B(3); D(); }
444 AutoMultiLock (A(0), A(1), A(2), A(3), A(4))
445 { C(5); B(0); B(1); B(2); B(3); B(4); D(); }
446 AutoMultiLock (A(0), A(1), A(2), A(3), A(4), A(5))
447 { C(6); B(0); B(1); B(2); B(3); B(4); B(5); D(); }
448 AutoMultiLock (A(0), A(1), A(2), A(3), A(4), A(5), A(6))
449 { C(7); B(0); B(1); B(2); B(3); B(4); B(5); B(6); D(); }
450 AutoMultiLock (A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7))
451 { C(8); B(0); B(1); B(2); B(3); B(4); B(5); B(6); B(7); D(); }
452 AutoMultiLock (A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8))
453 { C(9); B(0); B(1); B(2); B(3); B(4); B(5); B(6); B(7); B(8); D(); }
454 AutoMultiLock (A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9))
455 { C(10); B(0); B(1); B(2); B(3); B(4); B(5); B(6); B(7); B(8); B(9); D(); }
456
457 #undef D
458 #undef C
459 #undef B
460 #undef A
461
462 /**
463 * Releases all locks if not yet released by #leave() and
464 * destroys the instance.
465 */
466 ~AutoMultiLock()
467 {
468 /// @todo (dmik) this will change when we switch to RTSemRW*
469 if (mLocked)
470 RTCritSectLeaveMultiple (strlen (mM), mS);
471 }
472
473 /**
474 * Releases all locks temporarily in order to enter them again using
475 * #enter().
476 *
477 * @note There is no need to call this method unless you want later call
478 * #enter(). The destructor calls it automatically when necessary.
479 *
480 * @note Calling this method twice without calling #enter() in between will
481 * definitely fail.
482 *
483 * @note Unlike AutoLock::leave(), this method doesn't cause a complete
484 * release of all involved locks; if any of the locks was entered on the
485 * current thread prior constructing the AutoMultiLock instanve, they will
486 * remain acquired after this call! For this reason, using this method in
487 * the custom code doesn't make any practical sense.
488 *
489 * @todo Rename this method to unlock() and rename #enter() to lock()
490 * for similarity with AutoLock.
491 */
492 void leave()
493 {
494 AssertMsgReturn (mLocked, ("Already released all locks"), (void) 0);
495 /// @todo (dmik) this will change when we switch to RTSemRW*
496 RTCritSectLeaveMultiple (strlen (mM), mS);
497 mLocked = false;
498 }
499
500 /**
501 * Tries to enter all locks temporarily released by #unlock().
502 *
503 * @note This method succeeds only after #unlock() and always fails
504 * otherwise.
505 */
506 void enter()
507 {
508 AssertMsgReturn (!mLocked, ("Already entered all locks"), (void) 0);
509 ___CritSectEnterMulti (strlen (mM), mS);
510 mLocked = true;
511 }
512
513private:
514
515 AutoMultiLock();
516
517 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (AutoMultiLock)
518 DECLARE_CLS_NEW_DELETE_NOOP (AutoMultiLock)
519
520 RTCRITSECT *mS [tCnt];
521 char mM [tCnt + 1];
522 bool mLocked;
523
524 #undef ___CritSectEnterMulti
525};
526
527/**
528 * Disable instantiations of AutoMultiLock for zero and one
529 * number of locks.
530 */
531template<>
532class AutoMultiLock <0> { private : AutoMultiLock(); };
533
534template<>
535class AutoMultiLock <1> { private : AutoMultiLock(); };
536
537} // namespace util
538
539#endif // ____H_AUTOLOCK
540
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette