VirtualBox

Changeset 25285 in vbox


Ignore:
Timestamp:
Dec 9, 2009 11:38:58 PM (15 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
55832
Message:

Main: preparation for deadlock detection: convert AutoMultiWriteLock*: inheritance instead of template

Location:
trunk/src/VBox/Main
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Main/AutoLock.cpp

    r25284 r25285  
    9696
    9797typedef std::vector<LockHandle*> HandlesVector;
     98typedef std::vector<uint32_t> CountsVector;
    9899
    99100struct AutoLockBase::Data
    100101{
    101102    Data(size_t cHandles)
    102         : aHandles(cHandles),       // size of array
    103           fIsLocked(false),
    104           cUnlockedInLeave(0)
    105     { }
    106 
     103        : fIsLocked(false),
     104          aHandles(cHandles),       // size of array
     105          acUnlockedInLeave(cHandles)
     106    {
     107        for (uint32_t i = 0; i < cHandles; ++i)
     108        {
     109            acUnlockedInLeave[i] = 0;
     110            aHandles[i] = NULL;
     111        }
     112    }
     113
     114    bool            fIsLocked;          // if true, then all items in aHandles are locked by this AutoLock and
     115                                        // need to be unlocked in the destructor
    107116    HandlesVector   aHandles;           // array (vector) of LockHandle instances; in the case of AutoWriteLock
    108117                                        // and AutoReadLock, there will only be one item on the list; with the
    109118                                        // AutoMulti* derivatives, there will be multiple
    110     bool            fIsLocked;          // if true, then all items in aHandles are locked by this AutoLock and
    111                                         // need to be unlocked in the destructor
    112     uint32_t        cUnlockedInLeave;   // how many times the handle was unlocked in leave(); otherwise 0
     119    CountsVector    acUnlockedInLeave;  // for each lock handle, how many times the handle was unlocked in leave(); otherwise 0
    113120};
    114121
    115 AutoLockBase::AutoLockBase(LockHandle *pHandle)
    116 {
     122AutoLockBase::AutoLockBase(uint32_t cHandles)
     123{
     124    m = new Data(cHandles);
     125}
     126
     127AutoLockBase::AutoLockBase(uint32_t cHandles, LockHandle *pHandle)
     128{
     129    Assert(cHandles == 1);
    117130    m = new Data(1);
    118131    m->aHandles[0] = pHandle;
     
    176189void AutoLockBase::cleanup()
    177190{
    178     if (m->cUnlockedInLeave)
    179     {
    180         // there was a leave() before the destruction: then restore the
    181         // lock level that might have been set by locks other than our own
    182         if (m->fIsLocked)
    183             --m->cUnlockedInLeave;       // no lock for our own
    184         m->fIsLocked = false;
    185         for (; m->cUnlockedInLeave; --m->cUnlockedInLeave)
    186             callLockOnAllHandles();
    187     }
    188 
    189     if (m->fIsLocked)
     191    bool fAnyUnlockedInLeave = false;
     192
     193    uint32_t i = 0;
     194    for (HandlesVector::iterator it = m->aHandles.begin();
     195         it != m->aHandles.end();
     196         ++it)
     197    {
     198        LockHandle *pHandle = *it;
     199        if (pHandle)
     200        {
     201            if (m->acUnlockedInLeave[i])
     202            {
     203                // there was a leave() before the destruction: then restore the
     204                // lock level that might have been set by locks other than our own
     205                if (m->fIsLocked)
     206                {
     207                    --m->acUnlockedInLeave[i];
     208                    fAnyUnlockedInLeave = true;
     209                }
     210                for (; m->acUnlockedInLeave[i]; --m->acUnlockedInLeave[i])
     211                    callLockImpl(*pHandle);
     212            }
     213        }
     214        ++i;
     215    }
     216
     217    if (m->fIsLocked && !fAnyUnlockedInLeave)
    190218        callUnlockOnAllHandles();
    191219}
     
    286314{
    287315    l.unlockWrite();
    288 }
    289 
    290 /**
    291  * Returns @c true if the current thread holds a write lock on the managed
    292  * read/write semaphore. Returns @c false if the managed semaphore is @c
    293  * NULL.
    294  *
    295  * @note Intended for debugging only.
    296  */
    297 bool AutoWriteLockBase::isWriteLockOnCurrentThread() const
    298 {
    299     return m->aHandles[0] ? m->aHandles[0]->isWriteLockOnCurrentThread() : false;
    300 }
    301 
    302  /**
    303  * Returns the current write lock level of the managed smaphore. The lock
    304  * level determines the number of nested #lock() calls on the given
    305  * semaphore handle. Returns @c 0 if the managed semaphore is @c
    306  * NULL.
    307  *
    308  * Note that this call is valid only when the current thread owns a write
    309  * lock on the given semaphore handle and will assert otherwise.
    310  *
    311  * @note Intended for debugging only.
    312  */
    313 uint32_t AutoWriteLockBase::writeLockLevel() const
    314 {
    315     return m->aHandles[0] ? m->aHandles[0]->writeLockLevel() : 0;
    316316}
    317317
     
    340340{
    341341    AssertMsg(m->fIsLocked, ("m->fIsLocked is false, cannot leave()!"));
    342     AssertMsg(m->cUnlockedInLeave == 0, ("m->cUnlockedInLeave is %d, must be 0! Called leave() twice?", m->cUnlockedInLeave));
    343 
     342
     343    uint32_t i = 0;
    344344    for (HandlesVector::iterator it = m->aHandles.begin();
    345345         it != m->aHandles.end();
     
    349349        if (pHandle)
    350350        {
    351             m->cUnlockedInLeave = pHandle->writeLockLevel();
    352             AssertMsg(m->cUnlockedInLeave >= 1, ("m->cUnlockedInLeave is %d, must be >=1!", m->cUnlockedInLeave));
    353 
    354             for (uint32_t left = m->cUnlockedInLeave;
     351            AssertMsg(m->acUnlockedInLeave[i] == 0, ("m->cUnlockedInLeave[%d] is %d, must be 0! Called leave() twice?", i, m->acUnlockedInLeave[i]));
     352            m->acUnlockedInLeave[i] = pHandle->writeLockLevel();
     353            AssertMsg(m->acUnlockedInLeave[i] >= 1, ("m->cUnlockedInLeave[%d] is %d, must be >=1!", i, m->acUnlockedInLeave[i]));
     354
     355            for (uint32_t left = m->acUnlockedInLeave[i];
    355356                 left;
    356357                 --left)
    357358                pHandle->unlockWrite();
    358359        }
     360        ++i;
    359361    }
    360362}
     
    369371{
    370372    AssertMsg(m->fIsLocked, ("m->fIsLocked is false, cannot enter()!"));
    371     AssertMsg(m->cUnlockedInLeave != 0, ("m->cUnlockedInLeave is 0! enter() without leave()?"));
    372 
     373
     374    uint32_t i = 0;
    373375    for (HandlesVector::iterator it = m->aHandles.begin();
    374376         it != m->aHandles.end();
     
    378380        if (pHandle)
    379381        {
    380             for (; m->cUnlockedInLeave; --m->cUnlockedInLeave)
     382            AssertMsg(m->acUnlockedInLeave[i] != 0, ("m->cUnlockedInLeave[%d] is 0! enter() without leave()?", i));
     383
     384            for (; m->acUnlockedInLeave[i]; --m->acUnlockedInLeave[i])
    381385                pHandle->lockWrite();
    382386        }
     387        ++i;
     388    }
     389}
     390
     391/**
     392 * Same as #leave() but checks if the current thread actally owns the lock
     393 * and only proceeds in this case. As a result, as opposed to #leave(),
     394 * doesn't assert when called with no lock being held.
     395 */
     396void AutoWriteLockBase::maybeLeave()
     397{
     398    uint32_t i = 0;
     399    for (HandlesVector::iterator it = m->aHandles.begin();
     400         it != m->aHandles.end();
     401         ++it)
     402    {
     403        LockHandle *pHandle = *it;
     404        if (pHandle)
     405        {
     406            if (pHandle->isWriteLockOnCurrentThread())
     407            {
     408                m->acUnlockedInLeave[i] = pHandle->writeLockLevel();
     409                AssertMsg(m->acUnlockedInLeave[i] >= 1, ("m->cUnlockedInLeave[%d] is %d, must be >=1!", i, m->acUnlockedInLeave[i]));
     410
     411                for (uint32_t left = m->acUnlockedInLeave[i];
     412                     left;
     413                     --left)
     414                    pHandle->unlockWrite();
     415            }
     416        }
     417        ++i;
     418    }
     419}
     420
     421/**
     422 * Same as #enter() but checks if the current thread actally owns the lock
     423 * and only proceeds if not. As a result, as opposed to #enter(), doesn't
     424 * assert when called with the lock already being held.
     425 */
     426void AutoWriteLockBase::maybeEnter()
     427{
     428    uint32_t i = 0;
     429    for (HandlesVector::iterator it = m->aHandles.begin();
     430         it != m->aHandles.end();
     431         ++it)
     432    {
     433        LockHandle *pHandle = *it;
     434        if (pHandle)
     435        {
     436            if (!pHandle->isWriteLockOnCurrentThread())
     437            {
     438                for (; m->acUnlockedInLeave[i]; --m->acUnlockedInLeave[i])
     439                    pHandle->lockWrite();
     440            }
     441        }
     442        ++i;
    383443    }
    384444}
     
    425485}
    426486
     487/**
     488 * Returns @c true if the current thread holds a write lock on the managed
     489 * read/write semaphore. Returns @c false if the managed semaphore is @c
     490 * NULL.
     491 *
     492 * @note Intended for debugging only.
     493 */
     494bool AutoWriteLock::isWriteLockOnCurrentThread() const
     495{
     496    return m->aHandles[0] ? m->aHandles[0]->isWriteLockOnCurrentThread() : false;
     497}
     498
     499 /**
     500 * Returns the current write lock level of the managed smaphore. The lock
     501 * level determines the number of nested #lock() calls on the given
     502 * semaphore handle. Returns @c 0 if the managed semaphore is @c
     503 * NULL.
     504 *
     505 * Note that this call is valid only when the current thread owns a write
     506 * lock on the given semaphore handle and will assert otherwise.
     507 *
     508 * @note Intended for debugging only.
     509 */
     510uint32_t AutoWriteLock::writeLockLevel() const
     511{
     512    return m->aHandles[0] ? m->aHandles[0]->writeLockLevel() : 0;
     513}
     514
     515////////////////////////////////////////////////////////////////////////////////
     516//
     517// AutoMultiWriteLock*
     518//
     519////////////////////////////////////////////////////////////////////////////////
     520
     521AutoMultiWriteLock2::AutoMultiWriteLock2(Lockable *pl1, Lockable *pl2)
     522    : AutoWriteLockBase(2)
     523{
     524    if (pl1)
     525        m->aHandles[0] = pl1->lockHandle();
     526    if (pl2)
     527        m->aHandles[1] = pl2->lockHandle();
     528    acquire();
     529}
     530
     531AutoMultiWriteLock2::AutoMultiWriteLock2(LockHandle *pl1, LockHandle *pl2)
     532    : AutoWriteLockBase(2)
     533{
     534    m->aHandles[0] = pl1;
     535    m->aHandles[1] = pl2;
     536    acquire();
     537}
     538
     539AutoMultiWriteLock3::AutoMultiWriteLock3(Lockable *pl1, Lockable *pl2, Lockable *pl3)
     540    : AutoWriteLockBase(3)
     541{
     542    if (pl1)
     543        m->aHandles[0] = pl1->lockHandle();
     544    if (pl2)
     545        m->aHandles[1] = pl2->lockHandle();
     546    if (pl3)
     547        m->aHandles[2] = pl3->lockHandle();
     548    acquire();
     549}
     550
     551AutoMultiWriteLock3::AutoMultiWriteLock3(LockHandle *pl1, LockHandle *pl2, LockHandle *pl3)
     552    : AutoWriteLockBase(3)
     553{
     554    m->aHandles[0] = pl1;
     555    m->aHandles[1] = pl2;
     556    m->aHandles[2] = pl3;
     557    acquire();
     558}
     559
    427560} /* namespace util */
    428561/* vi: set tabstop=4 shiftwidth=4 expandtab: */
  • trunk/src/VBox/Main/include/AutoLock.h

    r25284 r25285  
    318318{
    319319protected:
    320     AutoLockBase(LockHandle *pHandle);
     320    AutoLockBase(uint32_t cHandles);
     321    AutoLockBase(uint32_t cHandles, LockHandle *pHandle);
    321322    virtual ~AutoLockBase();
    322323
     
    361362     */
    362363    AutoReadLock()
    363         : AutoLockBase(NULL)
     364        : AutoLockBase(1, NULL)
    364365    { }
    365366
     
    369370     */
    370371    AutoReadLock(LockHandle *aHandle)
    371         : AutoLockBase(aHandle)
     372        : AutoLockBase(1, aHandle)
    372373    {
    373374        acquire();
     
    379380     */
    380381    AutoReadLock(LockHandle &aHandle)
    381         : AutoLockBase(&aHandle)
     382        : AutoLockBase(1, &aHandle)
    382383    {
    383384        acquire();
     
    389390     */
    390391    AutoReadLock(const Lockable &aLockable)
    391         : AutoLockBase(aLockable.lockHandle())
     392        : AutoLockBase(1, aLockable.lockHandle())
    392393    {
    393394        acquire();
     
    399400     */
    400401    AutoReadLock(const Lockable *aLockable)
    401         : AutoLockBase(aLockable ? aLockable->lockHandle() : NULL)
     402        : AutoLockBase(1, aLockable ? aLockable->lockHandle() : NULL)
    402403    {
    403404        acquire();
     
    419420{
    420421protected:
    421     AutoWriteLockBase(LockHandle *pHandle)
    422         : AutoLockBase(pHandle)
     422    AutoWriteLockBase(uint32_t cHandles)
     423        : AutoLockBase(cHandles)
     424    { }
     425
     426    AutoWriteLockBase(uint32_t cHandles, LockHandle *pHandle)
     427        : AutoLockBase(cHandles, pHandle)
    423428    { }
    424429
     
    430435
    431436public:
    432     bool isWriteLockOnCurrentThread() const;
    433     uint32_t writeLockLevel() const;
    434 
    435437    void leave();
    436438    void enter();
    437 
    438     /**
    439      * Same as #leave() but checks if the current thread actally owns the lock
    440      * and only proceeds in this case. As a result, as opposed to #leave(),
    441      * doesn't assert when called with no lock being held.
    442      */
    443     void maybeLeave()
    444     {
    445         if (isWriteLockOnCurrentThread())
    446             leave();
    447     }
    448 
    449     /**
    450      * Same as #enter() but checks if the current thread actally owns the lock
    451      * and only proceeds if not. As a result, as opposed to #enter(), doesn't
    452      * assert when called with the lock already being held.
    453      */
    454     void maybeEnter()
    455     {
    456         if (!isWriteLockOnCurrentThread())
    457             enter();
    458     }
    459 
     439    void maybeLeave();
     440    void maybeEnter();
    460441};
    461442
     
    479460     */
    480461    AutoWriteLock()
    481         : AutoWriteLockBase(NULL)
     462        : AutoWriteLockBase(1, NULL)
    482463    { }
    483464
     
    487468     */
    488469    AutoWriteLock(LockHandle *aHandle)
    489         : AutoWriteLockBase(aHandle)
     470        : AutoWriteLockBase(1, aHandle)
    490471    {
    491472        acquire();
     
    497478     */
    498479    AutoWriteLock(LockHandle &aHandle)
    499         : AutoWriteLockBase(&aHandle)
     480        : AutoWriteLockBase(1, &aHandle)
    500481    {
    501482        acquire();
     
    507488     */
    508489    AutoWriteLock(const Lockable &aLockable)
    509         : AutoWriteLockBase(aLockable.lockHandle())
     490        : AutoWriteLockBase(1, aLockable.lockHandle())
    510491    {
    511492        acquire();
     
    517498     */
    518499    AutoWriteLock(const Lockable *aLockable)
    519         : AutoWriteLockBase(aLockable ? aLockable->lockHandle() : NULL)
     500        : AutoWriteLockBase(1, aLockable ? aLockable->lockHandle() : NULL)
    520501    {
    521502        acquire();
     
    557538
    558539    void attachRaw(LockHandle *ph);
    559 };
     540
     541    bool isWriteLockOnCurrentThread() const;
     542    uint32_t writeLockLevel() const;
     543};
     544
     545////////////////////////////////////////////////////////////////////////////////
     546//
     547// AutoMultiWriteLock*
     548//
     549////////////////////////////////////////////////////////////////////////////////
     550
     551class AutoMultiWriteLock2 : public AutoWriteLockBase
     552{
     553public:
     554    AutoMultiWriteLock2(Lockable *pl1, Lockable *pl2);
     555    AutoMultiWriteLock2(LockHandle *pl1, LockHandle *pl2);
     556
     557    virtual ~AutoMultiWriteLock2()
     558    {
     559        cleanup();
     560    }
     561};
     562
     563class AutoMultiWriteLock3 : public AutoWriteLockBase
     564{
     565public:
     566    AutoMultiWriteLock3(Lockable *pl1, Lockable *pl2, Lockable *pl3);
     567    AutoMultiWriteLock3(LockHandle *pl1, LockHandle *pl2, LockHandle *pl3);
     568
     569    virtual ~AutoMultiWriteLock3()
     570    {
     571        cleanup();
     572    }
     573};
     574
    560575
    561576////////////////////////////////////////////////////////////////////////////////
     
    710725#undef A
    711726
    712 ////////////////////////////////////////////////////////////////////////////////
    713 
    714 /**
    715  * Helper template class for AutoMultiWriteLockN classes.
    716  *
    717  * @param Cnt number of write semaphores to manage.
    718  */
    719 template <size_t Cnt>
    720 class AutoMultiWriteLockBase
    721 {
    722 
    723 public:
    724     /**
    725      * Calls AutoWriteLock::acquire() methods for all managed semaphore handles in
    726      * order they were passed to the constructor.
    727      */
    728     void acquire()
    729     {
    730         size_t i = 0;
    731         while (i < RT_ELEMENTS(mLocks))
    732             mLocks[i++].acquire();
    733     }
    734 
    735     /**
    736      * Calls AutoWriteLock::unlock() methods for all managed semaphore handles
    737      * in reverse to the order they were passed to the constructor.
    738      */
    739     void release()
    740     {
    741         AssertReturnVoid(RT_ELEMENTS(mLocks) > 0);
    742         size_t i = RT_ELEMENTS(mLocks);
    743         do
    744             mLocks[--i].release();
    745         while (i != 0);
    746     }
    747 
    748     /**
    749      * Calls AutoWriteLock::leave() methods for all managed semaphore handles in
    750      * reverse to the order they were passed to the constructor.
    751      */
    752     void leave()
    753     {
    754         AssertReturnVoid(RT_ELEMENTS(mLocks) > 0);
    755         size_t i = RT_ELEMENTS(mLocks);
    756         do
    757             mLocks[--i].leave();
    758         while (i != 0);
    759     }
    760 
    761     /**
    762     * Calls AutoWriteLock::maybeLeave() methods for all managed semaphore
    763     * handles in reverse to the order they were passed to the constructor.
    764      */
    765     void maybeLeave()
    766     {
    767         AssertReturnVoid(RT_ELEMENTS(mLocks) > 0);
    768         size_t i = RT_ELEMENTS(mLocks);
    769         do
    770             mLocks [-- i].maybeLeave();
    771         while (i != 0);
    772     }
    773 
    774     /**
    775      * Calls AutoWriteLock::maybeEnter() methods for all managed semaphore
    776      * handles in order they were passed to the constructor.
    777      */
    778     void maybeEnter()
    779     {
    780         size_t i = 0;
    781         while (i < RT_ELEMENTS(mLocks))
    782             mLocks[i++].maybeEnter();
    783     }
    784 
    785     /**
    786      * Calls AutoWriteLock::enter() methods for all managed semaphore handles in
    787      * order they were passed to the constructor.
    788      */
    789     void enter()
    790     {
    791         size_t i = 0;
    792         while (i < RT_ELEMENTS(mLocks))
    793             mLocks[i++].enter();
    794     }
    795 
    796 protected:
    797 
    798     AutoMultiWriteLockBase() {}
    799 
    800     void setLockHandle(size_t aIdx, LockHandle *aHandle)
    801     {
    802         mLocks[aIdx].attachRaw(aHandle);
    803     }
    804 
    805 private:
    806 
    807     AutoWriteLock mLocks[Cnt];
    808 
    809     DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (AutoMultiWriteLockBase)
    810     DECLARE_CLS_NEW_DELETE_NOOP (AutoMultiWriteLockBase)
    811 };
    812 
    813 /** AutoMultiWriteLockBase <0> is meaningless and forbidden. */
    814 template<>
    815 class AutoMultiWriteLockBase <0> { private : AutoMultiWriteLockBase(); };
    816 
    817 /** AutoMultiWriteLockBase <1> is meaningless and forbidden. */
    818 template<>
    819 class AutoMultiWriteLockBase <1> { private : AutoMultiWriteLockBase(); };
    820 
    821 ////////////////////////////////////////////////////////////////////////////////
    822 
    823 /* AutoMultiLockN class definitions */
    824 
    825 #define A(n) LockHandle *l##n
    826 #define B(n) setLockHandle (n, l##n)
    827 
    828 #define C(n) Lockable *l##n
    829 #define D(n) setLockHandle (n, l##n ? l##n->lockHandle() : NULL)
    830 
    831 /**
    832  * AutoMultiWriteLock for 2 locks.
    833  *
    834  * The AutoMultiWriteLockN family of classes provides a possibility to manage
    835  * several read/write semaphores at once. This is handy if all managed
    836  * semaphores need to be locked and unlocked synchronously and will also help to
    837  * avoid locking order errors.
    838  *
    839  * The functionality of the AutoMultiWriteLockN class family is similar to the
    840  * functionality of the AutoMultiLockN class family (see the AutoMultiLock2
    841  * class for details) with two important differences:
    842  * <ol>
    843  *     <li>Instances of AutoMultiWriteLockN classes are constructed from a list
    844  *     of LockHandle or Lockable arguments directly instead of getting
    845  *     intermediate LockOps interface pointers.
    846  *     </li>
    847  *     <li>All locks are requested in <b>write</b> mode.
    848  *     </li>
    849  *     <li>Since all locks are requested in write mode, bulk
    850  *     AutoMultiWriteLockBase::leave() and AutoMultiWriteLockBase::enter()
    851  *     operations are also available, that will leave and enter all managed
    852  *     semaphores at once in the proper order (similarly to
    853  *     AutoMultiWriteLockBase::lock() and AutoMultiWriteLockBase::unlock()).
    854  *     </li>
    855  * </ol>
    856  *
    857  * Here is a typical usage pattern:
    858  * <code>
    859  *  ...
    860  *  LockHandle data1, data2;
    861  *  ...
    862  *  {
    863  *      AutoMultiWriteLock2 multiLock (&data1, &data2);
    864  *      // both locks are held in write mode here
    865  *  }
    866  *  // both locks are released here
    867  * </code>
    868  */
    869 class AutoMultiWriteLock2 : public AutoMultiWriteLockBase <2>
    870 {
    871 public:
    872     AutoMultiWriteLock2 (A(0), A(1))
    873     { B(0); B(1); acquire(); }
    874     AutoMultiWriteLock2 (C(0), C(1))
    875     { D(0); D(1); acquire(); }
    876 };
    877 
    878 /** AutoMultiWriteLock for 3 locks. See AutoMultiWriteLock2 for more details. */
    879 class AutoMultiWriteLock3 : public AutoMultiWriteLockBase <3>
    880 {
    881 public:
    882     AutoMultiWriteLock3 (A(0), A(1), A(2))
    883     { B(0); B(1); B(2); acquire(); }
    884     AutoMultiWriteLock3 (C(0), C(1), C(2))
    885     { D(0); D(1); D(2); acquire(); }
    886 };
    887 
    888 /** AutoMultiWriteLock for 4 locks. See AutoMultiWriteLock2 for more details. */
    889 class AutoMultiWriteLock4 : public AutoMultiWriteLockBase <4>
    890 {
    891 public:
    892     AutoMultiWriteLock4 (A(0), A(1), A(2), A(3))
    893     { B(0); B(1); B(2); B(3); acquire(); }
    894     AutoMultiWriteLock4 (C(0), C(1), C(2), C(3))
    895     { D(0); D(1); D(2); D(3); acquire(); }
    896 };
    897 
    898 #undef D
    899 #undef C
    900 #undef B
    901 #undef A
    902 
    903727} /* namespace util */
    904728
Note: See TracChangeset for help on using the changeset viewer.

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