VirtualBox

Ignore:
Timestamp:
Jan 8, 2010 10:36:43 AM (15 years ago)
Author:
vboxsync
Message:

iprt/lockvalidator: Implement the lock stack (not recording recursion yet).

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Runtime/common/misc/lockvalidator.cpp

    r25685 r25689  
    241241*   Internal Functions                                                         *
    242242*******************************************************************************/
    243 static void rtLockValidatorClassDestroy(RTLOCKVALCLASSINT *pClass);
     243static void     rtLockValidatorClassDestroy(RTLOCKVALCLASSINT *pClass);
     244static uint32_t rtLockValidatorStackDepth(PRTTHREADINT pThread);
    244245
    245246
     
    342343 * @param   ...                 Format arguments.
    343344 */
    344 static void rtLockValidatorComplain(RT_SRC_POS_DECL, const char *pszWhat, ...)
     345static void rtLockValComplain(RT_SRC_POS_DECL, const char *pszWhat, ...)
    345346{
    346347    if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
     
    364365 * @param   pszSuffix           Message suffix.
    365366 */
    366 static void rtLockValidatorComplainAboutLock(const char *pszPrefix, PRTLOCKVALRECUNION pRec, const char *pszSuffix)
     367static void rtLockValComplainAboutLock(const char *pszPrefix, PRTLOCKVALRECUNION pRec, const char *pszSuffix)
    367368{
    368369    if (    VALID_PTR(pRec)
     
    413414
    414415/**
     416 * Dump the lock stack.
     417 *
     418 * @param   pThread             The thread which lock stack we're gonna dump.
     419 * @param   cchIndent           The indentation in chars.
     420 * @param   cMinFrames          The minimum number of frames to consider
     421 *                              dumping.
     422 */
     423static void rtLockValComplainAboutLockStack(PRTTHREADINT pThread, unsigned cchIndent, uint32_t cMinFrames)
     424{
     425    if (    VALID_PTR(pThread)
     426        &&  !ASMAtomicUoReadBool(&g_fLockValidatorQuiet)
     427        &&  pThread->u32Magic == RTTHREADINT_MAGIC
     428       )
     429    {
     430        uint32_t cEntries = rtLockValidatorStackDepth(pThread);
     431        if (cEntries >= cMinFrames)
     432        {
     433            RTAssertMsg2AddWeak("%*s---- start of lock stack - %u entr%s ----\n", cchIndent, "", cEntries,
     434                                cEntries == 1 ? "y" : "ies");
     435            PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop);
     436            for (uint32_t i = 0; pCur; i++)
     437            {
     438                char szPrefix[80];
     439                RTStrPrintf(szPrefix, sizeof(szPrefix), "%*s#%02u: ", cchIndent, "", i);
     440                rtLockValComplainAboutLock(szPrefix, pCur, "\n");
     441                switch (pCur->Core.u32Magic)
     442                {
     443                    case RTLOCKVALRECEXCL_MAGIC:    pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown);      break;
     444                    case RTLOCKVALRECSHRDOWN_MAGIC: pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown); break;
     445                    default:
     446                        RTAssertMsg2AddWeak("%*s<bad stack frame>\n", cchIndent, "");
     447                        pCur = NULL;
     448                        break;
     449                }
     450            }
     451            RTAssertMsg2AddWeak("%*s---- end of lock stack ----\n", cchIndent, "");
     452        }
     453    }
     454}
     455
     456
     457/**
    415458 * Launch the initial complaint.
    416459 *
     
    420463 * @param   pRec                The main lock involved. Can be NULL.
    421464 */
    422 static void rtLockValidatorComplainFirst(const char *pszWhat, PCRTLOCKVALSRCPOS pSrcPos, PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
     465static void rtLockValComplainFirst(const char *pszWhat, PCRTLOCKVALSRCPOS pSrcPos, PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
    423466{
    424467    if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
     
    430473        else
    431474            RTAssertMsg2Weak("%s  [thrd=%s]\n", pszWhat, VALID_PTR(pThreadSelf) ? pThreadSelf->szName : "<NIL>");
    432         rtLockValidatorComplainAboutLock("Lock: ", pRec, "\n");
     475        rtLockValComplainAboutLock("Lock: ", pRec, "\n");
     476        rtLockValComplainAboutLockStack(pThreadSelf, 0, 1);
    433477    }
    434478}
     
    441485 * @param   ...                 Format arguments.
    442486 */
    443 static void rtLockValidatorComplainMore(const char *pszFormat, ...)
     487static void rtLockValComplainMore(const char *pszFormat, ...)
    444488{
    445489    if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
     
    456500 * Raise a panic if enabled.
    457501 */
    458 static void rtLockValidatorComplainPanic(void)
     502static void rtLockValComplainPanic(void)
    459503{
    460504    if (ASMAtomicUoReadBool(&g_fLockValidatorMayPanic))
     
    645689    Assert(pPerThread->cReadLocks == 0);
    646690    Assert(pPerThread->fInValidator == false);
     691    Assert(pPerThread->pStackTop == NULL);
    647692}
    648693
     
    10291074
    10301075
    1031 static void rtLockValidatorStackPush(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec, PCRTLOCKVALSRCPOS pSrcPos)
     1076/**
     1077 * Calculates the depth of a lock stack.
     1078 *
     1079 * @returns Number of stack frames.
     1080 * @param   pThread         The thread.
     1081 */
     1082static uint32_t rtLockValidatorStackDepth(PRTTHREADINT pThread)
     1083{
     1084    uint32_t            cEntries = 0;
     1085    PRTLOCKVALRECUNION  pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop);
     1086    while (pCur)
     1087    {
     1088        switch (pCur->Core.u32Magic)
     1089        {
     1090            case RTLOCKVALRECEXCL_MAGIC:
     1091                pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown);
     1092                break;
     1093
     1094            case RTLOCKVALRECSHRDOWN_MAGIC:
     1095                pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown);
     1096                break;
     1097
     1098            default:
     1099                AssertMsgFailedReturn(("%#x\n", pCur->Core.u32Magic), cEntries);
     1100        }
     1101        cEntries++;
     1102    }
     1103    return cEntries;
     1104}
     1105
     1106
     1107/**
     1108 * Checks if the stack contains @a pRec.
     1109 *
     1110 * @returns true / false.
     1111 * @param   pThreadSelf         The curren thread.
     1112 * @param   pRec                The lock record.
     1113 */
     1114static bool rtLockValidatorStackContainsRec(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
     1115{
     1116    PRTLOCKVALRECUNION pCur = pThreadSelf->LockValidator.pStackTop;
     1117    while (pCur)
     1118    {
     1119        switch (pCur->Core.u32Magic)
     1120        {
     1121            case RTLOCKVALRECEXCL_MAGIC:
     1122                Assert(pRec->Excl.cRecursion >= 1);
     1123                if (pCur->Excl.pDown == pRec)
     1124                    return true;
     1125                pCur = pCur->Excl.pDown;
     1126                break;
     1127
     1128            case RTLOCKVALRECSHRDOWN_MAGIC:
     1129                Assert(pCur->ShrdOwner.cRecursion >= 1);
     1130                if (pCur->ShrdOwner.pDown == pRec)
     1131                    return true;
     1132                pCur = pCur->ShrdOwner.pDown;
     1133                break;
     1134
     1135            default:
     1136                AssertMsgFailedReturn(("%#x\n", pCur->Core.u32Magic), false);
     1137        }
     1138    }
     1139    return false;
     1140}
     1141
     1142
     1143/**
     1144 * Pushes a lock onto the stack.
     1145 *
     1146 * @param   pThreadSelf         The current thread.
     1147 * @param   pRec                The lock record.
     1148 */
     1149static void rtLockValidatorStackPush(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
    10321150{
    10331151    Assert(pThreadSelf == RTThreadSelf());
    1034 
    1035 }
    1036 
    1037 
     1152    Assert(!rtLockValidatorStackContainsRec(pThreadSelf, pRec));
     1153
     1154    switch (pRec->Core.u32Magic)
     1155    {
     1156        case RTLOCKVALRECEXCL_MAGIC:
     1157            Assert(pRec->Excl.cRecursion == 1);
     1158            Assert(pRec->Excl.pDown == NULL);
     1159            pRec->Excl.pDown = pThreadSelf->LockValidator.pStackTop;
     1160            break;
     1161
     1162        case RTLOCKVALRECSHRDOWN_MAGIC:
     1163            Assert(pRec->ShrdOwner.cRecursion == 1);
     1164            Assert(pRec->ShrdOwner.pDown == NULL);
     1165            pRec->ShrdOwner.pDown = pThreadSelf->LockValidator.pStackTop;
     1166            break;
     1167
     1168        case RTLOCKVALRECSHRD_MAGIC:
     1169        default:
     1170            AssertMsgFailedReturnVoid(("%#x\n",  pRec->Core.u32Magic));
     1171    }
     1172    pThreadSelf->LockValidator.pStackTop = pRec;
     1173}
     1174
     1175
     1176/**
     1177 * Pops a lock off the stack.
     1178 *
     1179 * @param   pThreadSelf         The current thread.
     1180 * @param   pRec                The lock.
     1181 */
    10381182static void rtLockValidatorStackPop(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
    10391183{
    10401184    Assert(pThreadSelf == RTThreadSelf());
    10411185
     1186    PRTLOCKVALRECUNION pDown;
     1187    switch (pRec->Core.u32Magic)
     1188    {
     1189        case RTLOCKVALRECEXCL_MAGIC:
     1190            Assert(pRec->Excl.cRecursion == 0);
     1191            pDown = pRec->Excl.pDown;
     1192            pRec->Excl.pDown = NULL;
     1193            break;
     1194
     1195        case RTLOCKVALRECSHRDOWN_MAGIC:
     1196            Assert(pRec->ShrdOwner.cRecursion == 0);
     1197            pDown = pRec->ShrdOwner.pDown;
     1198            pRec->ShrdOwner.pDown = NULL;
     1199            break;
     1200
     1201        default:
     1202            AssertMsgFailedReturnVoid(("%#x\n",  pRec->Core.u32Magic));
     1203    }
     1204    if (pThreadSelf->LockValidator.pStackTop == pRec)
     1205        pThreadSelf->LockValidator.pStackTop = pDown;
     1206    else
     1207    {
     1208        /* find the record on top of ours */
     1209        PRTLOCKVALRECUNION pAbove = pThreadSelf->LockValidator.pStackTop;
     1210        while (pAbove)
     1211        {
     1212            switch (pAbove->Core.u32Magic)
     1213            {
     1214                case RTLOCKVALRECEXCL_MAGIC:
     1215                    Assert(pRec->Excl.cRecursion >= 1);
     1216                    if (pAbove->Excl.pDown == pRec)
     1217                    {
     1218                        pAbove->Excl.pDown = pDown;
     1219                        return;
     1220                    }
     1221                    pAbove = pAbove->Excl.pDown;
     1222                    break;
     1223
     1224                case RTLOCKVALRECSHRDOWN_MAGIC:
     1225                    Assert(pAbove->ShrdOwner.cRecursion >= 1);
     1226                    if (pAbove->ShrdOwner.pDown == pRec)
     1227                    {
     1228                        pAbove->ShrdOwner.pDown = pDown;
     1229                        return;
     1230                    }
     1231                    pAbove = pAbove->ShrdOwner.pDown;
     1232                    break;
     1233
     1234                default:
     1235                    AssertMsgFailedReturnVoid(("%#x\n", pAbove->Core.u32Magic));
     1236            }
     1237        }
     1238        AssertMsgFailed(("%p %p\n", pRec, pThreadSelf));
     1239    }
    10421240}
    10431241
     
    10461244{
    10471245    Assert(pThreadSelf == RTThreadSelf());
    1048     /** @todo insert a recursion record onto the stack. Keep a reasonally big pool
     1246    /** @todo insert a recursion record onto the stack.  Keep a reasonally big pool
    10491247     *        of them associated with each thread. */
     1248    switch (pRec->Core.u32Magic)
     1249    {
     1250        case RTLOCKVALRECEXCL_MAGIC:
     1251            Assert(pRec->Excl.cRecursion > 1);
     1252            break;
     1253
     1254        case RTLOCKVALRECSHRDOWN_MAGIC:
     1255            Assert(pRec->ShrdOwner.cRecursion > 1);
     1256            break;
     1257
     1258        default:
     1259            AssertMsgFailedReturnVoid(("%#x\n",  pRec->Core.u32Magic));
     1260    }
    10501261}
    10511262
     
    10541265{
    10551266    Assert(pThreadSelf == RTThreadSelf());
    1056 
     1267    switch (pRec->Core.u32Magic)
     1268    {
     1269        case RTLOCKVALRECEXCL_MAGIC:
     1270            Assert(pRec->Excl.cRecursion >= 1);
     1271            break;
     1272
     1273        case RTLOCKVALRECSHRDOWN_MAGIC:
     1274            Assert(pRec->ShrdOwner.cRecursion >= 1);
     1275            break;
     1276
     1277        default:
     1278            AssertMsgFailedReturnVoid(("%#x\n",  pRec->Core.u32Magic));
     1279    }
    10571280}
    10581281
     
    11751398    {
    11761399        s_fComplained = true;
    1177         rtLockValidatorComplain(RT_SRC_POS, "lock validator stack is too small! (%zu entries)\n", RT_ELEMENTS(pStack->a));
     1400        rtLockValComplain(RT_SRC_POS, "lock validator stack is too small! (%zu entries)\n", RT_ELEMENTS(pStack->a));
    11781401    }
    11791402    return VINF_SUCCESS;
     
    14691692            default:            AssertFailed(); pszWhat = "!unexpected rc!"; break;
    14701693        }
    1471         rtLockValidatorComplainFirst(pszWhat, pSrcPos, pThreadSelf, pStack->a[0].pRec != pRec ? pRec : NULL);
    1472         rtLockValidatorComplainMore("---- start of deadlock chain - %u entries ----\n", pStack->c);
     1694        rtLockValComplainFirst(pszWhat, pSrcPos, pThreadSelf, pStack->a[0].pRec != pRec ? pRec : NULL);
     1695        rtLockValComplainMore("---- start of deadlock chain - %u entries ----\n", pStack->c);
    14731696        for (uint32_t i = 0; i < pStack->c; i++)
    14741697        {
     
    14791702                pShrdOwner = pStack->a[i].pRec->Shared.papOwners[pStack->a[i].iEntry];
    14801703            if (VALID_PTR(pShrdOwner) && pShrdOwner->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC)
    1481                 rtLockValidatorComplainAboutLock(szPrefix, (PRTLOCKVALRECUNION)pShrdOwner, "\n");
     1704                rtLockValComplainAboutLock(szPrefix, (PRTLOCKVALRECUNION)pShrdOwner, "\n");
    14821705            else
    1483                 rtLockValidatorComplainAboutLock(szPrefix, pStack->a[i].pRec, "\n");
    1484         }
    1485         rtLockValidatorComplainMore("---- end of deadlock chain ----\n");
    1486     }
    1487 
    1488     rtLockValidatorComplainPanic();
     1706                rtLockValComplainAboutLock(szPrefix, pStack->a[i].pRec, "\n");
     1707        }
     1708        rtLockValComplainMore("---- end of deadlock chain ----\n");
     1709    }
     1710
     1711    rtLockValComplainPanic();
    14891712}
    14901713
     
    16101833
    16111834    return VINF_SUCCESS;
     1835}
     1836
     1837
     1838/**
     1839 * Gets the lock name for the given record.
     1840 *
     1841 * @returns Read-only lock name.
     1842 * @param   pRec                The lock record.
     1843 */
     1844DECL_FORCE_INLINE(const char *) rtLockValidatorRecName(PRTLOCKVALRECUNION pRec)
     1845{
     1846    switch (pRec->Core.u32Magic)
     1847    {
     1848        case RTLOCKVALRECEXCL_MAGIC:
     1849            return pRec->Excl.pszName;
     1850        case RTLOCKVALRECSHRD_MAGIC:
     1851            return pRec->Shared.pszName;
     1852        case RTLOCKVALRECSHRDOWN_MAGIC:
     1853            return pRec->ShrdOwner.pSharedRec ? pRec->ShrdOwner.pSharedRec->pszName : "orphaned";
     1854        default:
     1855            return "unknown";
     1856    }
    16121857}
    16131858
     
    17111956        ASMAtomicWriteHandle(&pRecU->Excl.hThread, hThreadSelf);
    17121957
    1713         rtLockValidatorStackPush(hThreadSelf, pRecU, pSrcPos);
     1958        rtLockValidatorStackPush(hThreadSelf, pRecU);
    17141959    }
    17151960}
     
    17301975    if (c == 0)
    17311976    {
    1732         rtLockValidatorStackPopRecursion(pThread, pRec);
     1977        rtLockValidatorStackPop(pThread, pRec);
    17331978        ASMAtomicWriteHandle(&pRec->Excl.hThread, NIL_RTTHREAD);
    17341979    }
     
    17722017        && !pRecU->Excl.hClass->fRecursionOk)
    17732018    {
    1774         rtLockValidatorComplainFirst("Recursion not allowed by the class",
     2019        rtLockValComplainFirst("Recursion not allowed by the class",
    17752020                                     pSrcPos, pRecU->Excl.hThread, (PRTLOCKVALRECUNION)pRec);
    1776         rtLockValidatorComplainPanic();
     2021        rtLockValComplainPanic();
    17772022        return VERR_SEM_LV_NESTED;
    17782023    }
     
    17802025    Assert(pRecU->Excl.cRecursion < _1M);
    17812026    pRecU->Excl.cRecursion++;
    1782     rtLockValidatorStackPush(pRecU->Excl.hThread, pRecU, pSrcPos);
     2027    rtLockValidatorStackPushRecursion(pRecU->Excl.hThread, pRecU, pSrcPos);
    17832028    return VINF_SUCCESS;
    17842029}
     
    17942039    AssertReturn(pRecU->Excl.cRecursion > 1, VERR_SEM_LV_INVALID_PARAMETER);
    17952040
    1796     rtLockValidatorStackPop(pRecU->Excl.hThread, pRecU);
    17972041    pRecU->Excl.cRecursion--;
     2042    rtLockValidatorStackPopRecursion(pRecU->Excl.hThread, pRecU);
    17982043    return VINF_SUCCESS;
    17992044}
     
    18162061        && !pRecU->Excl.hClass->fRecursionOk)
    18172062    {
    1818         rtLockValidatorComplainFirst("Mixed recursion not allowed by the class",
     2063        rtLockValComplainFirst("Mixed recursion not allowed by the class",
    18192064                                     pSrcPos, pRecU->Excl.hThread, (PRTLOCKVALRECUNION)pRec);
    1820         rtLockValidatorComplainPanic();
     2065        rtLockValComplainPanic();
    18212066        return VERR_SEM_LV_NESTED;
    18222067    }
     
    18432088    AssertReturn(pRecU->Excl.cRecursion > 1, VERR_SEM_LV_INVALID_PARAMETER);
    18442089
     2090    pRecU->Excl.cRecursion--;
    18452091    rtLockValidatorStackPopRecursion(pRecU->Excl.hThread, pRecU);
    1846     pRecU->Excl.cRecursion--;
    18472092    return VINF_SUCCESS;
    18482093}
     
    19152160                && !pRecU->Excl.hClass->fRecursionOk))
    19162161        {
    1917             rtLockValidatorComplainFirst("Recursion not allowed", pSrcPos, pThreadSelf, pRecU);
    1918             rtLockValidatorComplainPanic();
     2162            rtLockValComplainFirst("Recursion not allowed", pSrcPos, pThreadSelf, pRecU);
     2163            rtLockValComplainPanic();
    19192164            rc = VERR_SEM_LV_NESTED;
    19202165        }
     
    21242369            )
    21252370        {
    2126             rtLockValidatorComplainFirst("Recursion not allowed", pSrcPos, pThreadSelf, pRecU);
    2127             rtLockValidatorComplainPanic();
     2371            rtLockValComplainFirst("Recursion not allowed", pSrcPos, pThreadSelf, pRecU);
     2372            rtLockValComplainPanic();
    21282373            rc =  VERR_SEM_LV_NESTED;
    21292374        }
     
    25042749        {
    25052750            if (!pRec->fSignaller)
    2506                 rtLockValidatorStackPush(hThread, pEntry, pSrcPos);
     2751                rtLockValidatorStackPush(hThread, pEntry);
    25072752        }
    25082753        else
     
    25272772    PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThread, &iEntry);
    25282773    AssertReturnVoid(pEntry);
    2529     if (pEntry->ShrdOwner.cRecursion > 1)
    2530     {
    2531         Assert(!pRec->fSignaller);
    2532         rtLockValidatorStackPopRecursion(hThread, pEntry);
    2533         pEntry->ShrdOwner.cRecursion--;
    2534     }
    2535     else
     2774    AssertReturnVoid(pEntry->ShrdOwner.cRecursion > 0);
     2775
     2776    uint32_t c = --pEntry->ShrdOwner.cRecursion;
     2777    if (c == 0)
    25362778    {
    25372779        if (!pRec->fSignaller)
    25382780            rtLockValidatorStackPop(hThread, (PRTLOCKVALRECUNION)pEntry);
    25392781        rtLockValidatorRecSharedRemoveAndFreeOwner(pRec, &pEntry->ShrdOwner, iEntry);
     2782    }
     2783    else
     2784    {
     2785        Assert(!pRec->fSignaller);
     2786        rtLockValidatorStackPopRecursion(hThread, pEntry);
    25402787    }
    25412788}
     
    25622809    if (RT_UNLIKELY(!pEntry))
    25632810    {
    2564         rtLockValidatorComplainFirst("Not owner (shared)", NULL, hThreadSelf, (PRTLOCKVALRECUNION)pRec);
    2565         rtLockValidatorComplainPanic();
     2811        rtLockValComplainFirst("Not owner (shared)", NULL, hThreadSelf, (PRTLOCKVALRECUNION)pRec);
     2812        rtLockValComplainPanic();
    25662813        return VERR_SEM_LV_NOT_OWNER;
    25672814    }
     
    25832830     */
    25842831    Assert(pEntry->ShrdOwner.cRecursion > 0);
    2585     if (pEntry->ShrdOwner.cRecursion > 1)
    2586     {
    2587         rtLockValidatorStackPopRecursion(hThreadSelf, pEntry);
    2588         pEntry->ShrdOwner.cRecursion--;
    2589     }
    2590     else
     2832    uint32_t c = --pEntry->ShrdOwner.cRecursion;
     2833    if (c == 0)
    25912834    {
    25922835        rtLockValidatorStackPop(hThreadSelf, pEntry);
    25932836        rtLockValidatorRecSharedRemoveAndFreeOwner(pRec, &pEntry->ShrdOwner, iEntry);
    25942837    }
     2838    else
     2839        rtLockValidatorStackPopRecursion(hThreadSelf, pEntry);
    25952840
    25962841    return VINF_SUCCESS;
     
    26172862    if (RT_UNLIKELY(!pEntry))
    26182863    {
    2619         rtLockValidatorComplainFirst("Invalid signaller", NULL, hThreadSelf, (PRTLOCKVALRECUNION)pRec);
    2620         rtLockValidatorComplainPanic();
     2864        rtLockValComplainFirst("Invalid signaller", NULL, hThreadSelf, (PRTLOCKVALRECUNION)pRec);
     2865        rtLockValComplainPanic();
    26212866        return VERR_SEM_LV_NOT_SIGNALLER;
    26222867    }
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