VirtualBox

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

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

InnoTek -> innotek: all the headers and comments.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.8 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
272private:
273
274 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (AutoLock)
275 DECLARE_CLS_NEW_DELETE_NOOP (AutoLock)
276
277 inline static RTCRITSECT *critSect (const Lockable *l)
278 {
279 Assert (l);
280 Handle *h = l->lockHandle();
281 return h ? &h->mCritSect : NULL;
282 }
283
284 RTCRITSECT *mCritSect;
285 uint32_t mLevel;
286 uint32_t mLeftLevel;
287
288 #undef ___CritSectEnter
289};
290
291/**
292 * Prototype. Later will be used to acquire a read-only lock
293 * (read-only locks differ from regular (write) locks so that more than one
294 * read lock can be acquired simultaneously provided that there are no
295 * active write locks).
296 * @todo Implement it!
297 */
298class AutoReaderLock : public AutoLock
299{
300public:
301
302 AutoReaderLock (const Handle &aHandle) : AutoLock (aHandle) {}
303 AutoReaderLock (const Handle *aHandle) : AutoLock (aHandle) {}
304
305 AutoReaderLock (const Lockable &aLockable) : AutoLock (aLockable) {}
306 AutoReaderLock (const Lockable *aLockable) : AutoLock (aLockable) {}
307
308private:
309
310 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (AutoReaderLock)
311 DECLARE_CLS_NEW_DELETE_NOOP (AutoReaderLock)
312};
313
314namespace internal
315{
316 /**
317 * @internal
318 * Special struct to differentiate between read and write locks
319 * in AutoMultiLock constructors.
320 */
321 struct LockableTag
322 {
323 LockableTag (const AutoLock::Handle *h, char m)
324 : handle (h), mode (m) {}
325 const AutoLock::Handle * const handle;
326 const char mode;
327 };
328}
329
330inline internal::LockableTag AutoLock::Handle::rlock() const
331{
332 return internal::LockableTag (this, 'r');
333}
334
335inline internal::LockableTag AutoLock::Handle::wlock() const
336{
337 return internal::LockableTag (this, 'w');
338}
339
340inline internal::LockableTag AutoLock::Lockable::rlock() const
341{
342 return internal::LockableTag (lockHandle(), 'r');
343}
344
345inline internal::LockableTag AutoLock::Lockable::wlock() const
346{
347 return internal::LockableTag (lockHandle(), 'w');
348}
349
350/**
351 * Smart template class to safely enter and leave a list of critical sections.
352 *
353 * When constructing an instance, it enters all given critical sections
354 * (in an "atomic", "all or none" fashion). These critical sections will be
355 * exited automatically when the instance goes out of scope (i.e. gets destroyed).
356 *
357 * It is possible to lock different critical sections in two different modes:
358 * for writing (as AutoLock does) or for reading (as AutoReaderLock does).
359 * The lock mode is determined by the method called on an AutoLock::Handle or
360 * AutoLock::Lockable instance when passing it to the AutoMultiLock constructor:
361 * |rlock()| to lock for reading or |wlock()| to lock for writing.
362 *
363 * Instances of this class are constructed as follows:
364 * <code>
365 * ...
366 * AutoLock::Handle data1, data2;
367 * ...
368 * {
369 * AutoMultiLock <2> multiLock (data1.wlock(), data2.rlock());
370 * // all locks are entered here:
371 * // data1 is entered in write mode (like AutoLock)
372 * // data2 is entered in read mode (like AutoReaderLock),
373 * }
374 * // all locks are exited here
375 * </code>
376 *
377 * The number of critical sections passed to the constructor must exactly
378 * match the number specified as the @a tCnt parameter of the template.
379 *
380 * @param tCnt number of critical sections to manage
381 */
382template <size_t tCnt>
383class AutoMultiLock
384{
385public:
386
387 #if defined(DEBUG)
388 # define ___CritSectEnterMulti(n, acs) \
389 RTCritSectEnterMultipleDebug ((n), (acs), \
390 "AutoMultiLock instantiation address >>>", 0, \
391 (RTUINTPTR) ASMReturnAddress())
392 #else
393 # define ___CritSectEnterMulti(n, acs) RTCritSectEnterMultiple ((n), (acs))
394 #endif
395
396 #define A(n) internal::LockableTag l##n
397 #define B(n) if (l##n.handle) { /* skip NULL tags */ \
398 mS[i] = &l##n.handle->mCritSect; \
399 mM[i++] = l##n.mode; \
400 } else
401 #define C(n) \
402 mLocked = true; \
403 mM [0] = 0; /* for safety in case of early return */ \
404 AssertMsg (tCnt == n, \
405 ("This AutoMultiLock is for %d locks, but %d were passed!\n", tCnt, n)); \
406 if (tCnt != n) return; \
407 int i = 0
408 /// @todo (dmik) this will change when we switch to RTSemRW*
409 #define D() mM [i] = 0; /* end of array */ \
410 ___CritSectEnterMulti (strlen (mM), mS)
411
412 AutoMultiLock (A(0), A(1))
413 { C(2); B(0); B(1); D(); }
414 AutoMultiLock (A(0), A(1), A(2))
415 { C(3); B(0); B(1); B(2); D(); }
416 AutoMultiLock (A(0), A(1), A(2), A(3))
417 { C(4); B(0); B(1); B(2); B(3); D(); }
418 AutoMultiLock (A(0), A(1), A(2), A(3), A(4))
419 { C(5); B(0); B(1); B(2); B(3); B(4); D(); }
420 AutoMultiLock (A(0), A(1), A(2), A(3), A(4), A(5))
421 { C(6); B(0); B(1); B(2); B(3); B(4); B(5); D(); }
422 AutoMultiLock (A(0), A(1), A(2), A(3), A(4), A(5), A(6))
423 { C(7); B(0); B(1); B(2); B(3); B(4); B(5); B(6); D(); }
424 AutoMultiLock (A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7))
425 { C(8); B(0); B(1); B(2); B(3); B(4); B(5); B(6); B(7); D(); }
426 AutoMultiLock (A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8))
427 { C(9); B(0); B(1); B(2); B(3); B(4); B(5); B(6); B(7); B(8); D(); }
428 AutoMultiLock (A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9))
429 { C(10); B(0); B(1); B(2); B(3); B(4); B(5); B(6); B(7); B(8); B(9); D(); }
430
431 #undef D
432 #undef C
433 #undef B
434 #undef A
435
436 /**
437 * Releases all locks if not yet released by #leave() and
438 * destroys the instance.
439 */
440 ~AutoMultiLock()
441 {
442 /// @todo (dmik) this will change when we switch to RTSemRW*
443 if (mLocked)
444 RTCritSectLeaveMultiple (strlen (mM), mS);
445 }
446
447 /**
448 * Releases all locks temporarily in order to enter them again using
449 * #enter().
450 *
451 * @note There is no need to call this method unless you want later call
452 * #enter(). The destructor calls it automatically when necessary.
453 *
454 * @note Calling this method twice without calling #enter() in between will
455 * definitely fail.
456 *
457 * @note Unlike AutoLock::leave(), this method doesn't cause a complete
458 * release of all involved locks; if any of the locks was entered on the
459 * current thread prior constructing the AutoMultiLock instanve, they will
460 * remain acquired after this call! For this reason, using this method in
461 * the custom code doesn't make any practical sense.
462 *
463 * @todo Rename this method to unlock() and rename #enter() to lock()
464 * for similarity with AutoLock.
465 */
466 void leave()
467 {
468 AssertMsgReturn (mLocked, ("Already released all locks"), (void) 0);
469 /// @todo (dmik) this will change when we switch to RTSemRW*
470 RTCritSectLeaveMultiple (strlen (mM), mS);
471 mLocked = false;
472 }
473
474 /**
475 * Tries to enter all locks temporarily released by #unlock().
476 *
477 * @note This method succeeds only after #unlock() and always fails
478 * otherwise.
479 */
480 void enter()
481 {
482 AssertMsgReturn (!mLocked, ("Already entered all locks"), (void) 0);
483 ___CritSectEnterMulti (strlen (mM), mS);
484 mLocked = true;
485 }
486
487private:
488
489 AutoMultiLock();
490
491 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (AutoMultiLock)
492 DECLARE_CLS_NEW_DELETE_NOOP (AutoMultiLock)
493
494 RTCRITSECT *mS [tCnt];
495 char mM [tCnt + 1];
496 bool mLocked;
497
498 #undef ___CritSectEnterMulti
499};
500
501/**
502 * Disable instantiations of AutoMultiLock for zero and one
503 * number of locks.
504 */
505template<>
506class AutoMultiLock <0> { private : AutoMultiLock(); };
507
508template<>
509class AutoMultiLock <1> { private : AutoMultiLock(); };
510
511} // namespace util
512
513#endif // ____H_AUTOLOCK
514
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