VirtualBox

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

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

Biggest check-in ever. New source code headers for all (C) innotek files.

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