VirtualBox

source: vbox/trunk/src/VBox/Main/glue/AutoLock.cpp@ 40274

Last change on this file since 40274 was 40257, checked in by vboxsync, 13 years ago

Main/Medium: rework locking scheme to solve lock order violations and long GUI start up time caused by too much locking
Main/all: Remove the enter and leave methods from write locks, they cause hard to find locking problems. Better solve them explicitly.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.7 KB
Line 
1/** @file
2 *
3 * Automatic locks, implementation
4 */
5
6/*
7 * Copyright (C) 2006-2012 Oracle Corporation
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 (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include <iprt/cdefs.h>
19#include <iprt/critsect.h>
20#include <iprt/thread.h>
21#include <iprt/semaphore.h>
22
23#include <iprt/err.h>
24#include <iprt/assert.h>
25
26#if defined(RT_LOCK_STRICT)
27# include <iprt/asm.h> // for ASMReturnAddress
28#endif
29
30#include <iprt/string.h>
31#include <iprt/path.h>
32#include <iprt/stream.h>
33
34#include "VBox/com/AutoLock.h"
35#include <VBox/com/string.h>
36
37#include <vector>
38#include <list>
39#include <map>
40
41namespace util
42{
43
44////////////////////////////////////////////////////////////////////////////////
45//
46// RuntimeLockClass
47//
48////////////////////////////////////////////////////////////////////////////////
49
50#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
51typedef std::map<VBoxLockingClass, RTLOCKVALCLASS> LockValidationClassesMap;
52LockValidationClassesMap g_mapLockValidationClasses;
53#endif
54
55/**
56 * Called from initterm.cpp on process initialization (on the main thread)
57 * to give us a chance to initialize lock validation runtime data.
58 */
59void InitAutoLockSystem()
60{
61#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
62 struct
63 {
64 VBoxLockingClass cls;
65 const char *pcszDescription;
66 } aClasses[] =
67 {
68 { LOCKCLASS_VIRTUALBOXOBJECT, "1-VIRTUALBOXOBJECT" },
69 { LOCKCLASS_HOSTOBJECT, "3-HOSTOBJECT" },
70 { LOCKCLASS_LISTOFMACHINES, "4-LISTOFMACHINES" },
71 { LOCKCLASS_MACHINEOBJECT, "5-MACHINEOBJECT" },
72 { LOCKCLASS_SNAPSHOTOBJECT, "6-SNAPSHOTOBJECT" },
73 { LOCKCLASS_MEDIUMQUERY, "7-MEDIUMQUERY" },
74 { LOCKCLASS_LISTOFMEDIA, "8-LISTOFMEDIA" },
75 { LOCKCLASS_LISTOFOTHEROBJECTS, "9-LISTOFOTHEROBJECTS" },
76 { LOCKCLASS_OTHEROBJECT, "10-OTHEROBJECT" },
77 { LOCKCLASS_USBLIST, "11-USBLIST" },
78 { LOCKCLASS_PROGRESSLIST, "12-PROGRESSLIST" },
79 { LOCKCLASS_OBJECTSTATE, "13-OBJECTSTATE" }
80 };
81
82 RTLOCKVALCLASS hClass;
83 int vrc;
84 for (unsigned i = 0; i < RT_ELEMENTS(aClasses); ++i)
85 {
86 vrc = RTLockValidatorClassCreate(&hClass,
87 true, /*fAutodidact*/
88 RT_SRC_POS,
89 aClasses[i].pcszDescription);
90 AssertRC(vrc);
91
92 // teach the new class that the classes created previously can be held
93 // while the new class is being acquired
94 for (LockValidationClassesMap::iterator it = g_mapLockValidationClasses.begin();
95 it != g_mapLockValidationClasses.end();
96 ++it)
97 {
98 RTLOCKVALCLASS &canBeHeld = it->second;
99 vrc = RTLockValidatorClassAddPriorClass(hClass,
100 canBeHeld);
101 AssertRC(vrc);
102 }
103
104 // and store the new class
105 g_mapLockValidationClasses[aClasses[i].cls] = hClass;
106 }
107
108/* WriteLockHandle critsect1(LOCKCLASS_VIRTUALBOXOBJECT);
109 WriteLockHandle critsect2(LOCKCLASS_VIRTUALBOXLIST);
110
111 AutoWriteLock lock1(critsect1 COMMA_LOCKVAL_SRC_POS);
112 AutoWriteLock lock2(critsect2 COMMA_LOCKVAL_SRC_POS);*/
113#endif
114}
115
116bool AutoLockHoldsLocksInClass(VBoxLockingClass lockClass)
117{
118#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
119 return RTLockValidatorHoldsLocksInClass(NIL_RTTHREAD,
120 g_mapLockValidationClasses[lockClass]);
121#else /* !VBOX_WITH_MAIN_LOCK_VALIDATION */
122 return false;
123#endif /* !VBOX_WITH_MAIN_LOCK_VALIDATION */
124}
125
126////////////////////////////////////////////////////////////////////////////////
127//
128// RWLockHandle
129//
130////////////////////////////////////////////////////////////////////////////////
131
132struct RWLockHandle::Data
133{
134 Data()
135 { }
136
137 RTSEMRW sem;
138 VBoxLockingClass lockClass;
139
140#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
141 com::Utf8Str strDescription;
142#endif
143};
144
145RWLockHandle::RWLockHandle(VBoxLockingClass lockClass)
146{
147 m = new Data();
148
149 m->lockClass = lockClass;
150
151#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
152 m->strDescription = com::Utf8StrFmt("r/w %RCv", this);
153 int vrc = RTSemRWCreateEx(&m->sem, 0 /*fFlags*/, g_mapLockValidationClasses[lockClass], RTLOCKVAL_SUB_CLASS_ANY, NULL);
154#else
155 int vrc = RTSemRWCreateEx(&m->sem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_ANY, NULL);
156#endif
157 AssertRC(vrc);
158}
159
160/*virtual*/ RWLockHandle::~RWLockHandle()
161{
162 RTSemRWDestroy(m->sem);
163 delete m;
164}
165
166/*virtual*/ bool RWLockHandle::isWriteLockOnCurrentThread() const
167{
168 return RTSemRWIsWriteOwner(m->sem);
169}
170
171/*virtual*/ void RWLockHandle::lockWrite(LOCKVAL_SRC_POS_DECL)
172{
173#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
174 int vrc = RTSemRWRequestWriteDebug(m->sem, RT_INDEFINITE_WAIT, (uintptr_t)ASMReturnAddress(), RT_SRC_POS_ARGS);
175#else
176 int vrc = RTSemRWRequestWrite(m->sem, RT_INDEFINITE_WAIT);
177#endif
178 AssertRC(vrc);
179}
180
181/*virtual*/ void RWLockHandle::unlockWrite()
182{
183 int vrc = RTSemRWReleaseWrite(m->sem);
184 AssertRC(vrc);
185
186}
187
188/*virtual*/ void RWLockHandle::lockRead(LOCKVAL_SRC_POS_DECL)
189{
190#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
191 int vrc = RTSemRWRequestReadDebug(m->sem, RT_INDEFINITE_WAIT, (uintptr_t)ASMReturnAddress(), RT_SRC_POS_ARGS);
192#else
193 int vrc = RTSemRWRequestRead(m->sem, RT_INDEFINITE_WAIT);
194#endif
195 AssertRC(vrc);
196}
197
198/*virtual*/ void RWLockHandle::unlockRead()
199{
200 int vrc = RTSemRWReleaseRead(m->sem);
201 AssertRC(vrc);
202}
203
204/*virtual*/ uint32_t RWLockHandle::writeLockLevel() const
205{
206 /* Note! This does not include read recursions done by the writer! */
207 return RTSemRWGetWriteRecursion(m->sem);
208}
209
210#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
211/*virtual*/ const char* RWLockHandle::describe() const
212{
213 return m->strDescription.c_str();
214}
215#endif
216
217////////////////////////////////////////////////////////////////////////////////
218//
219// WriteLockHandle
220//
221////////////////////////////////////////////////////////////////////////////////
222
223struct WriteLockHandle::Data
224{
225 Data()
226 { }
227
228 mutable RTCRITSECT sem;
229 VBoxLockingClass lockClass;
230
231#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
232 com::Utf8Str strDescription;
233#endif
234};
235
236WriteLockHandle::WriteLockHandle(VBoxLockingClass lockClass)
237{
238 m = new Data;
239
240 m->lockClass = lockClass;
241
242#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
243 m->strDescription = com::Utf8StrFmt("crit %RCv", this);
244 int vrc = RTCritSectInitEx(&m->sem, 0/*fFlags*/, g_mapLockValidationClasses[lockClass], RTLOCKVAL_SUB_CLASS_ANY, NULL);
245#else
246 int vrc = RTCritSectInitEx(&m->sem, 0/*fFlags*/, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_ANY, NULL);
247#endif
248 AssertRC(vrc);
249}
250
251WriteLockHandle::~WriteLockHandle()
252{
253 RTCritSectDelete(&m->sem);
254 delete m;
255}
256
257/*virtual*/ bool WriteLockHandle::isWriteLockOnCurrentThread() const
258{
259 return RTCritSectIsOwner(&m->sem);
260}
261
262/*virtual*/ void WriteLockHandle::lockWrite(LOCKVAL_SRC_POS_DECL)
263{
264#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
265 RTCritSectEnterDebug(&m->sem, (uintptr_t)ASMReturnAddress(), RT_SRC_POS_ARGS);
266#else
267 RTCritSectEnter(&m->sem);
268#endif
269}
270
271/*virtual*/ void WriteLockHandle::unlockWrite()
272{
273 RTCritSectLeave(&m->sem);
274}
275
276/*virtual*/ void WriteLockHandle::lockRead(LOCKVAL_SRC_POS_DECL)
277{
278 lockWrite(LOCKVAL_SRC_POS_ARGS);
279}
280
281/*virtual*/ void WriteLockHandle::unlockRead()
282{
283 unlockWrite();
284}
285
286/*virtual*/ uint32_t WriteLockHandle::writeLockLevel() const
287{
288 return RTCritSectGetRecursion(&m->sem);
289}
290
291#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
292/*virtual*/ const char* WriteLockHandle::describe() const
293{
294 return m->strDescription.c_str();
295}
296#endif
297
298////////////////////////////////////////////////////////////////////////////////
299//
300// AutoLockBase
301//
302////////////////////////////////////////////////////////////////////////////////
303
304typedef std::vector<LockHandle*> HandlesVector;
305
306struct AutoLockBase::Data
307{
308 Data(size_t cHandles
309#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
310 , const char *pcszFile_,
311 unsigned uLine_,
312 const char *pcszFunction_
313#endif
314 )
315 : fIsLocked(false),
316 aHandles(cHandles) // size of array
317#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
318 , pcszFile(pcszFile_),
319 uLine(uLine_),
320 pcszFunction(pcszFunction_)
321#endif
322 {
323 for (uint32_t i = 0; i < cHandles; ++i)
324 aHandles[i] = NULL;
325 }
326
327 bool fIsLocked; // if true, then all items in aHandles are locked by this AutoLock and
328 // need to be unlocked in the destructor
329 HandlesVector aHandles; // array (vector) of LockHandle instances; in the case of AutoWriteLock
330 // and AutoReadLock, there will only be one item on the list; with the
331 // AutoMulti* derivatives, there will be multiple
332
333#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
334 // information about where the lock occurred (passed down from the AutoLock classes)
335 const char *pcszFile;
336 unsigned uLine;
337 const char *pcszFunction;
338#endif
339};
340
341AutoLockBase::AutoLockBase(uint32_t cHandles
342 COMMA_LOCKVAL_SRC_POS_DECL)
343{
344 m = new Data(cHandles
345 COMMA_LOCKVAL_SRC_POS_ARGS);
346}
347
348AutoLockBase::AutoLockBase(uint32_t cHandles,
349 LockHandle *pHandle
350 COMMA_LOCKVAL_SRC_POS_DECL)
351{
352 Assert(cHandles == 1);
353 m = new Data(1
354 COMMA_LOCKVAL_SRC_POS_ARGS);
355 m->aHandles[0] = pHandle;
356}
357
358AutoLockBase::~AutoLockBase()
359{
360 delete m;
361}
362
363/**
364 * Requests ownership of all contained lock handles by calling
365 * the pure virtual callLockImpl() function on each of them,
366 * which must be implemented by the descendant class; in the
367 * implementation, AutoWriteLock will request a write lock
368 * whereas AutoReadLock will request a read lock.
369 *
370 * Does *not* modify the lock counts in the member variables.
371 */
372void AutoLockBase::callLockOnAllHandles()
373{
374 for (HandlesVector::iterator it = m->aHandles.begin();
375 it != m->aHandles.end();
376 ++it)
377 {
378 LockHandle *pHandle = *it;
379 if (pHandle)
380 // call virtual function implemented in AutoWriteLock or AutoReadLock
381 this->callLockImpl(*pHandle);
382 }
383}
384
385/**
386 * Releases ownership of all contained lock handles by calling
387 * the pure virtual callUnlockImpl() function on each of them,
388 * which must be implemented by the descendant class; in the
389 * implementation, AutoWriteLock will release a write lock
390 * whereas AutoReadLock will release a read lock.
391 *
392 * Does *not* modify the lock counts in the member variables.
393 */
394void AutoLockBase::callUnlockOnAllHandles()
395{
396 // unlock in reverse order!
397 for (HandlesVector::reverse_iterator it = m->aHandles.rbegin();
398 it != m->aHandles.rend();
399 ++it)
400 {
401 LockHandle *pHandle = *it;
402 if (pHandle)
403 // call virtual function implemented in AutoWriteLock or AutoReadLock
404 this->callUnlockImpl(*pHandle);
405 }
406}
407
408/**
409 * Destructor implementation that can also be called explicitly, if required.
410 * Restores the exact state before the AutoLock was created; that is, unlocks
411 * all contained semaphores.
412 */
413void AutoLockBase::cleanup()
414{
415 if (m->fIsLocked)
416 callUnlockOnAllHandles();
417}
418
419/**
420 * Requests ownership of all contained semaphores. Public method that can
421 * only be called once and that also gets called by the AutoLock constructors.
422 */
423void AutoLockBase::acquire()
424{
425 AssertMsg(!m->fIsLocked, ("m->fIsLocked is true, attempting to lock twice!"));
426 callLockOnAllHandles();
427 m->fIsLocked = true;
428}
429
430/**
431 * Releases ownership of all contained semaphores. Public method.
432 */
433void AutoLockBase::release()
434{
435 AssertMsg(m->fIsLocked, ("m->fIsLocked is false, cannot release!"));
436 callUnlockOnAllHandles();
437 m->fIsLocked = false;
438}
439
440////////////////////////////////////////////////////////////////////////////////
441//
442// AutoReadLock
443//
444////////////////////////////////////////////////////////////////////////////////
445
446/**
447 * Release all read locks acquired by this instance through the #lock()
448 * call and destroys the instance.
449 *
450 * Note that if there there are nested #lock() calls without the
451 * corresponding number of #unlock() calls when the destructor is called, it
452 * will assert. This is because having an unbalanced number of nested locks
453 * is a program logic error which must be fixed.
454 */
455/*virtual*/ AutoReadLock::~AutoReadLock()
456{
457 LockHandle *pHandle = m->aHandles[0];
458
459 if (pHandle)
460 {
461 if (m->fIsLocked)
462 callUnlockImpl(*pHandle);
463 }
464}
465
466/**
467 * Implementation of the pure virtual declared in AutoLockBase.
468 * This gets called by AutoLockBase.acquire() to actually request
469 * the semaphore; in the AutoReadLock implementation, we request
470 * the semaphore in read mode.
471 */
472/*virtual*/ void AutoReadLock::callLockImpl(LockHandle &l)
473{
474#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
475 l.lockRead(m->pcszFile, m->uLine, m->pcszFunction);
476#else
477 l.lockRead();
478#endif
479}
480
481/**
482 * Implementation of the pure virtual declared in AutoLockBase.
483 * This gets called by AutoLockBase.release() to actually release
484 * the semaphore; in the AutoReadLock implementation, we release
485 * the semaphore in read mode.
486 */
487/*virtual*/ void AutoReadLock::callUnlockImpl(LockHandle &l)
488{
489 l.unlockRead();
490}
491
492////////////////////////////////////////////////////////////////////////////////
493//
494// AutoWriteLockBase
495//
496////////////////////////////////////////////////////////////////////////////////
497
498/**
499 * Implementation of the pure virtual declared in AutoLockBase.
500 * This gets called by AutoLockBase.acquire() to actually request
501 * the semaphore; in the AutoWriteLock implementation, we request
502 * the semaphore in write mode.
503 */
504/*virtual*/ void AutoWriteLockBase::callLockImpl(LockHandle &l)
505{
506#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
507 l.lockWrite(m->pcszFile, m->uLine, m->pcszFunction);
508#else
509 l.lockWrite();
510#endif
511}
512
513/**
514 * Implementation of the pure virtual declared in AutoLockBase.
515 * This gets called by AutoLockBase.release() to actually release
516 * the semaphore; in the AutoWriteLock implementation, we release
517 * the semaphore in write mode.
518 */
519/*virtual*/ void AutoWriteLockBase::callUnlockImpl(LockHandle &l)
520{
521 l.unlockWrite();
522}
523
524////////////////////////////////////////////////////////////////////////////////
525//
526// AutoWriteLock
527//
528////////////////////////////////////////////////////////////////////////////////
529
530AutoWriteLock::AutoWriteLock(uint32_t cHandles,
531 LockHandle** pHandles
532 COMMA_LOCKVAL_SRC_POS_DECL)
533 : AutoWriteLockBase(cHandles
534 COMMA_LOCKVAL_SRC_POS_ARGS)
535{
536 Assert(cHandles);
537 Assert(pHandles);
538
539 for (uint32_t i = 0; i < cHandles; ++i)
540 m->aHandles[i] = pHandles[i];
541
542 acquire();
543}
544
545
546
547/**
548 * Attaches another handle to this auto lock instance.
549 *
550 * The previous object's lock is completely released before the new one is
551 * acquired. The lock level of the new handle will be the same. This
552 * also means that if the lock was not acquired at all before #attach(), it
553 * will not be acquired on the new handle too.
554 *
555 * @param aHandle New handle to attach.
556 */
557void AutoWriteLock::attach(LockHandle *aHandle)
558{
559 LockHandle *pHandle = m->aHandles[0];
560
561 /* detect simple self-reattachment */
562 if (pHandle != aHandle)
563 {
564 bool fWasLocked = m->fIsLocked;
565
566 cleanup();
567
568 m->aHandles[0] = aHandle;
569 m->fIsLocked = fWasLocked;
570
571 if (aHandle)
572 if (fWasLocked)
573 callLockImpl(*aHandle);
574 }
575}
576
577/**
578 * Returns @c true if the current thread holds a write lock on the managed
579 * read/write semaphore. Returns @c false if the managed semaphore is @c
580 * NULL.
581 *
582 * @note Intended for debugging only.
583 */
584bool AutoWriteLock::isWriteLockOnCurrentThread() const
585{
586 return m->aHandles[0] ? m->aHandles[0]->isWriteLockOnCurrentThread() : false;
587}
588
589 /**
590 * Returns the current write lock level of the managed semaphore. The lock
591 * level determines the number of nested #lock() calls on the given
592 * semaphore handle. Returns @c 0 if the managed semaphore is @c
593 * NULL.
594 *
595 * Note that this call is valid only when the current thread owns a write
596 * lock on the given semaphore handle and will assert otherwise.
597 *
598 * @note Intended for debugging only.
599 */
600uint32_t AutoWriteLock::writeLockLevel() const
601{
602 return m->aHandles[0] ? m->aHandles[0]->writeLockLevel() : 0;
603}
604
605////////////////////////////////////////////////////////////////////////////////
606//
607// AutoMultiWriteLock*
608//
609////////////////////////////////////////////////////////////////////////////////
610
611AutoMultiWriteLock2::AutoMultiWriteLock2(Lockable *pl1,
612 Lockable *pl2
613 COMMA_LOCKVAL_SRC_POS_DECL)
614 : AutoWriteLockBase(2
615 COMMA_LOCKVAL_SRC_POS_ARGS)
616{
617 if (pl1)
618 m->aHandles[0] = pl1->lockHandle();
619 if (pl2)
620 m->aHandles[1] = pl2->lockHandle();
621 acquire();
622}
623
624AutoMultiWriteLock2::AutoMultiWriteLock2(LockHandle *pl1,
625 LockHandle *pl2
626 COMMA_LOCKVAL_SRC_POS_DECL)
627 : AutoWriteLockBase(2
628 COMMA_LOCKVAL_SRC_POS_ARGS)
629{
630 m->aHandles[0] = pl1;
631 m->aHandles[1] = pl2;
632 acquire();
633}
634
635AutoMultiWriteLock3::AutoMultiWriteLock3(Lockable *pl1,
636 Lockable *pl2,
637 Lockable *pl3
638 COMMA_LOCKVAL_SRC_POS_DECL)
639 : AutoWriteLockBase(3
640 COMMA_LOCKVAL_SRC_POS_ARGS)
641{
642 if (pl1)
643 m->aHandles[0] = pl1->lockHandle();
644 if (pl2)
645 m->aHandles[1] = pl2->lockHandle();
646 if (pl3)
647 m->aHandles[2] = pl3->lockHandle();
648 acquire();
649}
650
651AutoMultiWriteLock3::AutoMultiWriteLock3(LockHandle *pl1,
652 LockHandle *pl2,
653 LockHandle *pl3
654 COMMA_LOCKVAL_SRC_POS_DECL)
655 : AutoWriteLockBase(3
656 COMMA_LOCKVAL_SRC_POS_ARGS)
657{
658 m->aHandles[0] = pl1;
659 m->aHandles[1] = pl2;
660 m->aHandles[2] = pl3;
661 acquire();
662}
663
664AutoMultiWriteLock4::AutoMultiWriteLock4(Lockable *pl1,
665 Lockable *pl2,
666 Lockable *pl3,
667 Lockable *pl4
668 COMMA_LOCKVAL_SRC_POS_DECL)
669 : AutoWriteLockBase(4
670 COMMA_LOCKVAL_SRC_POS_ARGS)
671{
672 if (pl1)
673 m->aHandles[0] = pl1->lockHandle();
674 if (pl2)
675 m->aHandles[1] = pl2->lockHandle();
676 if (pl3)
677 m->aHandles[2] = pl3->lockHandle();
678 if (pl4)
679 m->aHandles[3] = pl4->lockHandle();
680 acquire();
681}
682
683AutoMultiWriteLock4::AutoMultiWriteLock4(LockHandle *pl1,
684 LockHandle *pl2,
685 LockHandle *pl3,
686 LockHandle *pl4
687 COMMA_LOCKVAL_SRC_POS_DECL)
688 : AutoWriteLockBase(4
689 COMMA_LOCKVAL_SRC_POS_ARGS)
690{
691 m->aHandles[0] = pl1;
692 m->aHandles[1] = pl2;
693 m->aHandles[2] = pl3;
694 m->aHandles[3] = pl4;
695 acquire();
696}
697
698} /* namespace util */
699/* vi: set tabstop=4 shiftwidth=4 expandtab: */
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