diff options
Diffstat (limited to 'src/VBox/Runtime/common/misc/lockvalidator.cpp')
-rw-r--r-- | src/VBox/Runtime/common/misc/lockvalidator.cpp | 4472 |
1 files changed, 4472 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/misc/lockvalidator.cpp b/src/VBox/Runtime/common/misc/lockvalidator.cpp new file mode 100644 index 00000000..1680d2b2 --- /dev/null +++ b/src/VBox/Runtime/common/misc/lockvalidator.cpp @@ -0,0 +1,4472 @@ +/* $Id: lockvalidator.cpp $ */ +/** @file + * IPRT - Lock Validator. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/lockvalidator.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/thread.h> + +#include "internal/lockvalidator.h" +#include "internal/magics.h" +#include "internal/strhash.h" +#include "internal/thread.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Macro that asserts that a pointer is aligned correctly. + * Only used when fighting bugs. */ +#if 1 +# define RTLOCKVAL_ASSERT_PTR_ALIGN(p) \ + AssertMsg(!((uintptr_t)(p) & (sizeof(uintptr_t) - 1)), ("%p\n", (p))); +#else +# define RTLOCKVAL_ASSERT_PTR_ALIGN(p) do { } while (0) +#endif + +/** Hashes the class handle (pointer) into an apPriorLocksHash index. */ +#define RTLOCKVALCLASS_HASH(hClass) \ + ( ((uintptr_t)(hClass) >> 6 ) \ + % ( RT_SIZEOFMEMB(RTLOCKVALCLASSINT, apPriorLocksHash) \ + / sizeof(PRTLOCKVALCLASSREF)) ) + +/** The max value for RTLOCKVALCLASSINT::cRefs. */ +#define RTLOCKVALCLASS_MAX_REFS UINT32_C(0xffff0000) +/** The max value for RTLOCKVALCLASSREF::cLookups. */ +#define RTLOCKVALCLASSREF_MAX_LOOKUPS UINT32_C(0xfffe0000) +/** The absolute max value for RTLOCKVALCLASSREF::cLookups at which it will + * be set back to RTLOCKVALCLASSREF_MAX_LOOKUPS. */ +#define RTLOCKVALCLASSREF_MAX_LOOKUPS_FIX UINT32_C(0xffff0000) + + +/** @def RTLOCKVAL_WITH_RECURSION_RECORDS + * Enable recursion records. */ +#if defined(IN_RING3) || defined(DOXYGEN_RUNNING) +# define RTLOCKVAL_WITH_RECURSION_RECORDS 1 +#endif + +/** @def RTLOCKVAL_WITH_VERBOSE_DUMPS + * Enables some extra verbosity in the lock dumping. */ +#if defined(DOXYGEN_RUNNING) +# define RTLOCKVAL_WITH_VERBOSE_DUMPS +#endif + +/** @def RTLOCKVAL_WITH_CLASS_HASH_STATS + * Enables collection prior class hash lookup statistics, dumping them when + * complaining about the class. */ +#if defined(DEBUG) || defined(DOXYGEN_RUNNING) +# define RTLOCKVAL_WITH_CLASS_HASH_STATS +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Deadlock detection stack entry. + */ +typedef struct RTLOCKVALDDENTRY +{ + /** The current record. */ + PRTLOCKVALRECUNION pRec; + /** The current entry number if pRec is a shared one. */ + uint32_t iEntry; + /** The thread state of the thread we followed to get to pFirstSibling. + * This is only used for validating a deadlock stack. */ + RTTHREADSTATE enmState; + /** The thread we followed to get to pFirstSibling. + * This is only used for validating a deadlock stack. */ + PRTTHREADINT pThread; + /** What pThread is waiting on, i.e. where we entered the circular list of + * siblings. This is used for validating a deadlock stack as well as + * terminating the sibling walk. */ + PRTLOCKVALRECUNION pFirstSibling; +} RTLOCKVALDDENTRY; + + +/** + * Deadlock detection stack. + */ +typedef struct RTLOCKVALDDSTACK +{ + /** The number stack entries. */ + uint32_t c; + /** The stack entries. */ + RTLOCKVALDDENTRY a[32]; +} RTLOCKVALDDSTACK; +/** Pointer to a deadlock detection stack. */ +typedef RTLOCKVALDDSTACK *PRTLOCKVALDDSTACK; + + +/** + * Reference to another class. + */ +typedef struct RTLOCKVALCLASSREF +{ + /** The class. */ + RTLOCKVALCLASS hClass; + /** The number of lookups of this class. */ + uint32_t volatile cLookups; + /** Indicates whether the entry was added automatically during order checking + * (true) or manually via the API (false). */ + bool fAutodidacticism; + /** Reserved / explicit alignment padding. */ + bool afReserved[3]; +} RTLOCKVALCLASSREF; +/** Pointer to a class reference. */ +typedef RTLOCKVALCLASSREF *PRTLOCKVALCLASSREF; + + +/** Pointer to a chunk of class references. */ +typedef struct RTLOCKVALCLASSREFCHUNK *PRTLOCKVALCLASSREFCHUNK; +/** + * Chunk of class references. + */ +typedef struct RTLOCKVALCLASSREFCHUNK +{ + /** Array of refs. */ +#if 0 /** @todo for testing allocation of new chunks. */ + RTLOCKVALCLASSREF aRefs[ARCH_BITS == 32 ? 10 : 8]; +#else + RTLOCKVALCLASSREF aRefs[2]; +#endif + /** Pointer to the next chunk. */ + PRTLOCKVALCLASSREFCHUNK volatile pNext; +} RTLOCKVALCLASSREFCHUNK; + + +/** + * Lock class. + */ +typedef struct RTLOCKVALCLASSINT +{ + /** AVL node core. */ + AVLLU32NODECORE Core; + /** Magic value (RTLOCKVALCLASS_MAGIC). */ + uint32_t volatile u32Magic; + /** Reference counter. See RTLOCKVALCLASS_MAX_REFS. */ + uint32_t volatile cRefs; + /** Whether the class is allowed to teach it self new locking order rules. */ + bool fAutodidact; + /** Whether to allow recursion. */ + bool fRecursionOk; + /** Strict release order. */ + bool fStrictReleaseOrder; + /** Whether this class is in the tree. */ + bool fInTree; + /** Donate a reference to the next retainer. This is a hack to make + * RTLockValidatorClassCreateUnique work. */ + bool volatile fDonateRefToNextRetainer; + /** Reserved future use / explicit alignment. */ + bool afReserved[3]; + /** The minimum wait interval for which we do deadlock detection + * (milliseconds). */ + RTMSINTERVAL cMsMinDeadlock; + /** The minimum wait interval for which we do order checks (milliseconds). */ + RTMSINTERVAL cMsMinOrder; + /** More padding. */ + uint32_t au32Reserved[ARCH_BITS == 32 ? 5 : 2]; + /** Classes that may be taken prior to this one. + * This is a linked list where each node contains a chunk of locks so that we + * reduce the number of allocations as well as localize the data. */ + RTLOCKVALCLASSREFCHUNK PriorLocks; + /** Hash table containing frequently encountered prior locks. */ + PRTLOCKVALCLASSREF apPriorLocksHash[17]; + /** Class name. (Allocated after the end of the block as usual.) */ + char const *pszName; + /** Where this class was created. + * This is mainly used for finding automatically created lock classes. + * @remarks The strings are stored after this structure so we won't crash + * if the class lives longer than the module (dll/so/dylib) that + * spawned it. */ + RTLOCKVALSRCPOS CreatePos; +#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS + /** Hash hits. */ + uint32_t volatile cHashHits; + /** Hash misses. */ + uint32_t volatile cHashMisses; +#endif +} RTLOCKVALCLASSINT; +AssertCompileSize(AVLLU32NODECORE, ARCH_BITS == 32 ? 20 : 32); +AssertCompileMemberOffset(RTLOCKVALCLASSINT, PriorLocks, 64); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Serializing object destruction and deadlock detection. + * + * This makes sure that none of the memory examined by the deadlock detection + * code will become invalid (reused for other purposes or made not present) + * while the detection is in progress. + * + * NS: RTLOCKVALREC*, RTTHREADINT and RTLOCKVALDRECSHRD::papOwners destruction. + * EW: Deadlock detection and some related activities. + */ +static RTSEMXROADS g_hLockValidatorXRoads = NIL_RTSEMXROADS; +/** Serializing class tree insert and lookups. */ +static RTSEMRW g_hLockValClassTreeRWLock= NIL_RTSEMRW; +/** Class tree. */ +static PAVLLU32NODECORE g_LockValClassTree = NULL; +/** Critical section serializing the teaching new rules to the classes. */ +static RTCRITSECT g_LockValClassTeachCS; + +/** Whether the lock validator is enabled or disabled. + * Only applies to new locks. */ +static bool volatile g_fLockValidatorEnabled = true; +/** Set if the lock validator is quiet. */ +#ifdef RT_STRICT +static bool volatile g_fLockValidatorQuiet = false; +#else +static bool volatile g_fLockValidatorQuiet = true; +#endif +/** Set if the lock validator may panic. */ +#ifdef RT_STRICT +static bool volatile g_fLockValidatorMayPanic = true; +#else +static bool volatile g_fLockValidatorMayPanic = false; +#endif +/** Whether to return an error status on wrong locking order. */ +static bool volatile g_fLockValSoftWrongOrder = false; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void rtLockValidatorClassDestroy(RTLOCKVALCLASSINT *pClass); +static uint32_t rtLockValidatorStackDepth(PRTTHREADINT pThread); + + +/** + * Lazy initialization of the lock validator globals. + */ +static void rtLockValidatorLazyInit(void) +{ + static uint32_t volatile s_fInitializing = false; + if (ASMAtomicCmpXchgU32(&s_fInitializing, true, false)) + { + /* + * The locks. + */ + if (!RTCritSectIsInitialized(&g_LockValClassTeachCS)) + RTCritSectInitEx(&g_LockValClassTeachCS, RTCRITSECT_FLAGS_NO_LOCK_VAL, NIL_RTLOCKVALCLASS, + RTLOCKVAL_SUB_CLASS_ANY, "RTLockVal-Teach"); + + if (g_hLockValClassTreeRWLock == NIL_RTSEMRW) + { + RTSEMRW hSemRW; + int rc = RTSemRWCreateEx(&hSemRW, RTSEMRW_FLAGS_NO_LOCK_VAL, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_ANY, "RTLockVal-Tree"); + if (RT_SUCCESS(rc)) + ASMAtomicWriteHandle(&g_hLockValClassTreeRWLock, hSemRW); + } + + if (g_hLockValidatorXRoads == NIL_RTSEMXROADS) + { + RTSEMXROADS hXRoads; + int rc = RTSemXRoadsCreate(&hXRoads); + if (RT_SUCCESS(rc)) + ASMAtomicWriteHandle(&g_hLockValidatorXRoads, hXRoads); + } + +#ifdef IN_RING3 + /* + * Check the environment for our config variables. + */ + if (RTEnvExist("IPRT_LOCK_VALIDATOR_ENABLED")) + ASMAtomicWriteBool(&g_fLockValidatorEnabled, true); + if (RTEnvExist("IPRT_LOCK_VALIDATOR_DISABLED")) + ASMAtomicWriteBool(&g_fLockValidatorEnabled, false); + + if (RTEnvExist("IPRT_LOCK_VALIDATOR_MAY_PANIC")) + ASMAtomicWriteBool(&g_fLockValidatorMayPanic, true); + if (RTEnvExist("IPRT_LOCK_VALIDATOR_MAY_NOT_PANIC")) + ASMAtomicWriteBool(&g_fLockValidatorMayPanic, false); + + if (RTEnvExist("IPRT_LOCK_VALIDATOR_NOT_QUIET")) + ASMAtomicWriteBool(&g_fLockValidatorQuiet, false); + if (RTEnvExist("IPRT_LOCK_VALIDATOR_QUIET")) + ASMAtomicWriteBool(&g_fLockValidatorQuiet, true); + + if (RTEnvExist("IPRT_LOCK_VALIDATOR_STRICT_ORDER")) + ASMAtomicWriteBool(&g_fLockValSoftWrongOrder, false); + if (RTEnvExist("IPRT_LOCK_VALIDATOR_SOFT_ORDER")) + ASMAtomicWriteBool(&g_fLockValSoftWrongOrder, true); +#endif + + /* + * Register cleanup + */ + /** @todo register some cleanup callback if we care. */ + + ASMAtomicWriteU32(&s_fInitializing, false); + } +} + + + +/** Wrapper around ASMAtomicReadPtr. */ +DECL_FORCE_INLINE(PRTLOCKVALRECUNION) rtLockValidatorReadRecUnionPtr(PRTLOCKVALRECUNION volatile *ppRec) +{ + PRTLOCKVALRECUNION p = ASMAtomicReadPtrT(ppRec, PRTLOCKVALRECUNION); + RTLOCKVAL_ASSERT_PTR_ALIGN(p); + return p; +} + + +/** Wrapper around ASMAtomicWritePtr. */ +DECL_FORCE_INLINE(void) rtLockValidatorWriteRecUnionPtr(PRTLOCKVALRECUNION volatile *ppRec, PRTLOCKVALRECUNION pRecNew) +{ + RTLOCKVAL_ASSERT_PTR_ALIGN(pRecNew); + ASMAtomicWritePtr(ppRec, pRecNew); +} + + +/** Wrapper around ASMAtomicReadPtr. */ +DECL_FORCE_INLINE(PRTTHREADINT) rtLockValidatorReadThreadHandle(RTTHREAD volatile *phThread) +{ + PRTTHREADINT p = ASMAtomicReadPtrT(phThread, PRTTHREADINT); + RTLOCKVAL_ASSERT_PTR_ALIGN(p); + return p; +} + + +/** Wrapper around ASMAtomicUoReadPtr. */ +DECL_FORCE_INLINE(PRTLOCKVALRECSHRDOWN) rtLockValidatorUoReadSharedOwner(PRTLOCKVALRECSHRDOWN volatile *ppOwner) +{ + PRTLOCKVALRECSHRDOWN p = ASMAtomicUoReadPtrT(ppOwner, PRTLOCKVALRECSHRDOWN); + RTLOCKVAL_ASSERT_PTR_ALIGN(p); + return p; +} + + +/** + * Reads a volatile thread handle field and returns the thread name. + * + * @returns Thread name (read only). + * @param phThread The thread handle field. + */ +static const char *rtLockValidatorNameThreadHandle(RTTHREAD volatile *phThread) +{ + PRTTHREADINT pThread = rtLockValidatorReadThreadHandle(phThread); + if (!pThread) + return "<NIL>"; + if (!VALID_PTR(pThread)) + return "<INVALID>"; + if (pThread->u32Magic != RTTHREADINT_MAGIC) + return "<BAD-THREAD-MAGIC>"; + return pThread->szName; +} + + +/** + * Launch a simple assertion like complaint w/ panic. + * + * @param SRC_POS The source position where call is being made from. + * @param pszWhat What we're complaining about. + * @param ... Format arguments. + */ +static void rtLockValComplain(RT_SRC_POS_DECL, const char *pszWhat, ...) +{ + if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet)) + { + RTAssertMsg1Weak("RTLockValidator", iLine, pszFile, pszFunction); + va_list va; + va_start(va, pszWhat); + RTAssertMsg2WeakV(pszWhat, va); + va_end(va); + } + if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet)) + RTAssertPanic(); +} + + +/** + * Describes the class. + * + * @param pszPrefix Message prefix. + * @param pClass The class to complain about. + * @param uSubClass My sub-class. + * @param fVerbose Verbose description including relations to other + * classes. + */ +static void rtLockValComplainAboutClass(const char *pszPrefix, RTLOCKVALCLASSINT *pClass, uint32_t uSubClass, bool fVerbose) +{ + if (ASMAtomicUoReadBool(&g_fLockValidatorQuiet)) + return; + + /* Stringify the sub-class. */ + const char *pszSubClass; + char szSubClass[32]; + if (uSubClass < RTLOCKVAL_SUB_CLASS_USER) + switch (uSubClass) + { + case RTLOCKVAL_SUB_CLASS_NONE: pszSubClass = "none"; break; + case RTLOCKVAL_SUB_CLASS_ANY: pszSubClass = "any"; break; + default: + RTStrPrintf(szSubClass, sizeof(szSubClass), "invl-%u", uSubClass); + pszSubClass = szSubClass; + break; + } + else + { + RTStrPrintf(szSubClass, sizeof(szSubClass), "%u", uSubClass); + pszSubClass = szSubClass; + } + + /* Validate the class pointer. */ + if (!VALID_PTR(pClass)) + { + RTAssertMsg2AddWeak("%sbad class=%p sub-class=%s\n", pszPrefix, pClass, pszSubClass); + return; + } + if (pClass->u32Magic != RTLOCKVALCLASS_MAGIC) + { + RTAssertMsg2AddWeak("%sbad class=%p magic=%#x sub-class=%s\n", pszPrefix, pClass, pClass->u32Magic, pszSubClass); + return; + } + + /* OK, dump the class info. */ + RTAssertMsg2AddWeak("%sclass=%p %s created={%Rbn(%u) %Rfn %p} sub-class=%s\n", pszPrefix, + pClass, + pClass->pszName, + pClass->CreatePos.pszFile, + pClass->CreatePos.uLine, + pClass->CreatePos.pszFunction, + pClass->CreatePos.uId, + pszSubClass); + if (fVerbose) + { + uint32_t i = 0; + uint32_t cPrinted = 0; + for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; pChunk; pChunk = pChunk->pNext) + for (unsigned j = 0; j < RT_ELEMENTS(pChunk->aRefs); j++, i++) + { + RTLOCKVALCLASSINT *pCurClass = pChunk->aRefs[j].hClass; + if (pCurClass != NIL_RTLOCKVALCLASS) + { + RTAssertMsg2AddWeak("%s%s #%02u: %s, %s, %u lookup%s\n", pszPrefix, + cPrinted == 0 + ? "Prior:" + : " ", + i, + pCurClass->pszName, + pChunk->aRefs[j].fAutodidacticism + ? "autodidactic" + : "manually ", + pChunk->aRefs[j].cLookups, + pChunk->aRefs[j].cLookups != 1 ? "s" : ""); + cPrinted++; + } + } + if (!cPrinted) + RTAssertMsg2AddWeak("%sPrior: none\n", pszPrefix); +#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS + RTAssertMsg2AddWeak("%sHash Stats: %u hits, %u misses\n", pszPrefix, pClass->cHashHits, pClass->cHashMisses); +#endif + } + else + { + uint32_t cPrinted = 0; + for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; pChunk; pChunk = pChunk->pNext) + for (unsigned j = 0; j < RT_ELEMENTS(pChunk->aRefs); j++) + { + RTLOCKVALCLASSINT *pCurClass = pChunk->aRefs[j].hClass; + if (pCurClass != NIL_RTLOCKVALCLASS) + { + if ((cPrinted % 10) == 0) + RTAssertMsg2AddWeak("%sPrior classes: %s%s", pszPrefix, pCurClass->pszName, + pChunk->aRefs[j].fAutodidacticism ? "*" : ""); + else if ((cPrinted % 10) != 9) + RTAssertMsg2AddWeak(", %s%s", pCurClass->pszName, + pChunk->aRefs[j].fAutodidacticism ? "*" : ""); + else + RTAssertMsg2AddWeak(", %s%s\n", pCurClass->pszName, + pChunk->aRefs[j].fAutodidacticism ? "*" : ""); + cPrinted++; + } + } + if (!cPrinted) + RTAssertMsg2AddWeak("%sPrior classes: none\n", pszPrefix); + else if ((cPrinted % 10) != 0) + RTAssertMsg2AddWeak("\n"); + } +} + + +/** + * Helper for getting the class name. + * @returns Class name string. + * @param pClass The class. + */ +static const char *rtLockValComplainGetClassName(RTLOCKVALCLASSINT *pClass) +{ + if (!pClass) + return "<nil-class>"; + if (!VALID_PTR(pClass)) + return "<bad-class-ptr>"; + if (pClass->u32Magic != RTLOCKVALCLASS_MAGIC) + return "<bad-class-magic>"; + if (!pClass->pszName) + return "<no-class-name>"; + return pClass->pszName; +} + +/** + * Formats the sub-class. + * + * @returns Stringified sub-class. + * @param uSubClass The name. + * @param pszBuf Buffer that is big enough. + */ +static const char *rtLockValComplainGetSubClassName(uint32_t uSubClass, char *pszBuf) +{ + if (uSubClass < RTLOCKVAL_SUB_CLASS_USER) + switch (uSubClass) + { + case RTLOCKVAL_SUB_CLASS_NONE: return "none"; + case RTLOCKVAL_SUB_CLASS_ANY: return "any"; + default: + RTStrPrintf(pszBuf, 32, "invl-%u", uSubClass); + break; + } + else + RTStrPrintf(pszBuf, 32, "%x", uSubClass); + return pszBuf; +} + + +/** + * Helper for rtLockValComplainAboutLock. + */ +DECL_FORCE_INLINE(void) rtLockValComplainAboutLockHlp(const char *pszPrefix, PRTLOCKVALRECUNION pRec, const char *pszSuffix, + uint32_t u32Magic, PCRTLOCKVALSRCPOS pSrcPos, uint32_t cRecursion, + const char *pszFrameType) +{ + char szBuf[32]; + switch (u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: +#ifdef RTLOCKVAL_WITH_VERBOSE_DUMPS + RTAssertMsg2AddWeak("%s%p %s xrec=%p own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [x%s]%s", pszPrefix, + pRec->Excl.hLock, pRec->Excl.szName, pRec, + rtLockValidatorNameThreadHandle(&pRec->Excl.hThread), cRecursion, + rtLockValComplainGetClassName(pRec->Excl.hClass), + rtLockValComplainGetSubClassName(pRec->Excl.uSubClass, szBuf), + pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId, + pszFrameType, pszSuffix); +#else + RTAssertMsg2AddWeak("%s%p %s own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [x%s]%s", pszPrefix, + pRec->Excl.hLock, pRec->Excl.szName, + rtLockValidatorNameThreadHandle(&pRec->Excl.hThread), cRecursion, + rtLockValComplainGetClassName(pRec->Excl.hClass), + rtLockValComplainGetSubClassName(pRec->Excl.uSubClass, szBuf), + pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId, + pszFrameType, pszSuffix); +#endif + break; + + case RTLOCKVALRECSHRD_MAGIC: + RTAssertMsg2AddWeak("%ss %p %s srec=%p cls=%s/%s [s%s]%s", pszPrefix, + pRec->Shared.hLock, pRec->Shared.szName, pRec, + rtLockValComplainGetClassName(pRec->Shared.hClass), + rtLockValComplainGetSubClassName(pRec->Shared.uSubClass, szBuf), + pszFrameType, pszSuffix); + break; + + case RTLOCKVALRECSHRDOWN_MAGIC: + { + PRTLOCKVALRECSHRD pShared = pRec->ShrdOwner.pSharedRec; + if ( VALID_PTR(pShared) + && pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC) +#ifdef RTLOCKVAL_WITH_VERBOSE_DUMPS + RTAssertMsg2AddWeak("%s%p %s srec=%p trec=%p own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [o%s]%s", pszPrefix, + pShared->hLock, pShared->szName, pShared, + pRec, rtLockValidatorNameThreadHandle(&pRec->ShrdOwner.hThread), cRecursion, + rtLockValComplainGetClassName(pShared->hClass), + rtLockValComplainGetSubClassName(pShared->uSubClass, szBuf), + pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId, + pszSuffix, pszSuffix); +#else + RTAssertMsg2AddWeak("%s%p %s own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [o%s]%s", pszPrefix, + pShared->hLock, pShared->szName, + rtLockValidatorNameThreadHandle(&pRec->ShrdOwner.hThread), cRecursion, + rtLockValComplainGetClassName(pShared->hClass), + rtLockValComplainGetSubClassName(pShared->uSubClass, szBuf), + pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId, + pszFrameType, pszSuffix); +#endif + else + RTAssertMsg2AddWeak("%sbad srec=%p trec=%p own=%s r=%u pos={%Rbn(%u) %Rfn %p} [x%s]%s", pszPrefix, + pShared, + pRec, rtLockValidatorNameThreadHandle(&pRec->ShrdOwner.hThread), cRecursion, + pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId, + pszFrameType, pszSuffix); + break; + } + + default: + AssertMsgFailed(("%#x\n", u32Magic)); + } +} + + +/** + * Describes the lock. + * + * @param pszPrefix Message prefix. + * @param pRec The lock record we're working on. + * @param pszSuffix Message suffix. + */ +static void rtLockValComplainAboutLock(const char *pszPrefix, PRTLOCKVALRECUNION pRec, const char *pszSuffix) +{ +#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS +# define FIX_REC(r) 1 +#else +# define FIX_REC(r) (r) +#endif + if ( VALID_PTR(pRec) + && !ASMAtomicUoReadBool(&g_fLockValidatorQuiet)) + { + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + rtLockValComplainAboutLockHlp(pszPrefix, pRec, pszSuffix, RTLOCKVALRECEXCL_MAGIC, + &pRec->Excl.SrcPos, FIX_REC(pRec->Excl.cRecursion), ""); + break; + + case RTLOCKVALRECSHRD_MAGIC: + rtLockValComplainAboutLockHlp(pszPrefix, pRec, pszSuffix, RTLOCKVALRECSHRD_MAGIC, NULL, 0, ""); + break; + + case RTLOCKVALRECSHRDOWN_MAGIC: + rtLockValComplainAboutLockHlp(pszPrefix, pRec, pszSuffix, RTLOCKVALRECSHRDOWN_MAGIC, + &pRec->ShrdOwner.SrcPos, FIX_REC(pRec->ShrdOwner.cRecursion), ""); + break; + + case RTLOCKVALRECNEST_MAGIC: + { + PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec; + uint32_t u32Magic; + if ( VALID_PTR(pRealRec) + && ( (u32Magic = pRealRec->Core.u32Magic) == RTLOCKVALRECEXCL_MAGIC + || u32Magic == RTLOCKVALRECSHRD_MAGIC + || u32Magic == RTLOCKVALRECSHRDOWN_MAGIC) + ) + rtLockValComplainAboutLockHlp(pszPrefix, pRealRec, pszSuffix, u32Magic, + &pRec->Nest.SrcPos, pRec->Nest.cRecursion, "/r"); + else + RTAssertMsg2AddWeak("%sbad rrec=%p nrec=%p r=%u pos={%Rbn(%u) %Rfn %p}%s", pszPrefix, + pRealRec, pRec, pRec->Nest.cRecursion, + pRec->Nest.SrcPos.pszFile, pRec->Nest.SrcPos.uLine, pRec->Nest.SrcPos.pszFunction, pRec->Nest.SrcPos.uId, + pszSuffix); + break; + } + + default: + RTAssertMsg2AddWeak("%spRec=%p u32Magic=%#x (bad)%s", pszPrefix, pRec, pRec->Core.u32Magic, pszSuffix); + break; + } + } +#undef FIX_REC +} + + +/** + * Dump the lock stack. + * + * @param pThread The thread which lock stack we're gonna dump. + * @param cchIndent The indentation in chars. + * @param cMinFrames The minimum number of frames to consider + * dumping. + * @param pHighightRec Record that should be marked specially in the + * dump. + */ +static void rtLockValComplainAboutLockStack(PRTTHREADINT pThread, unsigned cchIndent, uint32_t cMinFrames, + PRTLOCKVALRECUNION pHighightRec) +{ + if ( VALID_PTR(pThread) + && !ASMAtomicUoReadBool(&g_fLockValidatorQuiet) + && pThread->u32Magic == RTTHREADINT_MAGIC + ) + { + uint32_t cEntries = rtLockValidatorStackDepth(pThread); + if (cEntries >= cMinFrames) + { + RTAssertMsg2AddWeak("%*s---- start of lock stack for %p %s - %u entr%s ----\n", cchIndent, "", + pThread, pThread->szName, cEntries, cEntries == 1 ? "y" : "ies"); + PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop); + for (uint32_t i = 0; VALID_PTR(pCur); i++) + { + char szPrefix[80]; + RTStrPrintf(szPrefix, sizeof(szPrefix), "%*s#%02u: ", cchIndent, "", i); + rtLockValComplainAboutLock(szPrefix, pCur, pHighightRec != pCur ? "\n" : " (*)\n"); + switch (pCur->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown); break; + case RTLOCKVALRECSHRDOWN_MAGIC: pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown); break; + case RTLOCKVALRECNEST_MAGIC: pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown); break; + default: + RTAssertMsg2AddWeak("%*s<bad stack frame>\n", cchIndent, ""); + pCur = NULL; + break; + } + } + RTAssertMsg2AddWeak("%*s---- end of lock stack ----\n", cchIndent, ""); + } + } +} + + +/** + * Launch the initial complaint. + * + * @param pszWhat What we're complaining about. + * @param pSrcPos Where we are complaining from, as it were. + * @param pThreadSelf The calling thread. + * @param pRec The main lock involved. Can be NULL. + * @param fDumpStack Whether to dump the lock stack (true) or not + * (false). + */ +static void rtLockValComplainFirst(const char *pszWhat, PCRTLOCKVALSRCPOS pSrcPos, PRTTHREADINT pThreadSelf, + PRTLOCKVALRECUNION pRec, bool fDumpStack) +{ + if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet)) + { + ASMCompilerBarrier(); /* paranoia */ + RTAssertMsg1Weak("RTLockValidator", pSrcPos ? pSrcPos->uLine : 0, pSrcPos ? pSrcPos->pszFile : NULL, pSrcPos ? pSrcPos->pszFunction : NULL); + if (pSrcPos && pSrcPos->uId) + RTAssertMsg2Weak("%s [uId=%p thrd=%s]\n", pszWhat, pSrcPos->uId, VALID_PTR(pThreadSelf) ? pThreadSelf->szName : "<NIL>"); + else + RTAssertMsg2Weak("%s [thrd=%s]\n", pszWhat, VALID_PTR(pThreadSelf) ? pThreadSelf->szName : "<NIL>"); + rtLockValComplainAboutLock("Lock: ", pRec, "\n"); + if (fDumpStack) + rtLockValComplainAboutLockStack(pThreadSelf, 0, 1, pRec); + } +} + + +/** + * Continue bitching. + * + * @param pszFormat Format string. + * @param ... Format arguments. + */ +static void rtLockValComplainMore(const char *pszFormat, ...) +{ + if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet)) + { + va_list va; + va_start(va, pszFormat); + RTAssertMsg2AddWeakV(pszFormat, va); + va_end(va); + } +} + + +/** + * Raise a panic if enabled. + */ +static void rtLockValComplainPanic(void) +{ + if (ASMAtomicUoReadBool(&g_fLockValidatorMayPanic)) + RTAssertPanic(); +} + + +/** + * Copy a source position record. + * + * @param pDst The destination. + * @param pSrc The source. Can be NULL. + */ +DECL_FORCE_INLINE(void) rtLockValidatorSrcPosCopy(PRTLOCKVALSRCPOS pDst, PCRTLOCKVALSRCPOS pSrc) +{ + if (pSrc) + { + ASMAtomicUoWriteU32(&pDst->uLine, pSrc->uLine); + ASMAtomicUoWritePtr(&pDst->pszFile, pSrc->pszFile); + ASMAtomicUoWritePtr(&pDst->pszFunction, pSrc->pszFunction); + ASMAtomicUoWritePtr((void * volatile *)&pDst->uId, (void *)pSrc->uId); + } + else + { + ASMAtomicUoWriteU32(&pDst->uLine, 0); + ASMAtomicUoWriteNullPtr(&pDst->pszFile); + ASMAtomicUoWriteNullPtr(&pDst->pszFunction); + ASMAtomicUoWritePtr(&pDst->uId, (RTHCUINTPTR)0); + } +} + + +/** + * Init a source position record. + * + * @param pSrcPos The source position record. + */ +DECL_FORCE_INLINE(void) rtLockValidatorSrcPosInit(PRTLOCKVALSRCPOS pSrcPos) +{ + pSrcPos->pszFile = NULL; + pSrcPos->pszFunction = NULL; + pSrcPos->uId = 0; + pSrcPos->uLine = 0; +#if HC_ARCH_BITS == 64 + pSrcPos->u32Padding = 0; +#endif +} + + +/** + * Hashes the specified source position. + * + * @returns Hash. + * @param pSrcPos The source position record. + */ +static uint32_t rtLockValidatorSrcPosHash(PCRTLOCKVALSRCPOS pSrcPos) +{ + uint32_t uHash; + if ( ( pSrcPos->pszFile + || pSrcPos->pszFunction) + && pSrcPos->uLine != 0) + { + uHash = 0; + if (pSrcPos->pszFile) + uHash = sdbmInc(pSrcPos->pszFile, uHash); + if (pSrcPos->pszFunction) + uHash = sdbmInc(pSrcPos->pszFunction, uHash); + uHash += pSrcPos->uLine; + } + else + { + Assert(pSrcPos->uId); + uHash = (uint32_t)pSrcPos->uId; + } + + return uHash; +} + + +/** + * Compares two source positions. + * + * @returns 0 if equal, < 0 if pSrcPos1 is smaller than pSrcPos2, > 0 if + * otherwise. + * @param pSrcPos1 The first source position. + * @param pSrcPos2 The second source position. + */ +static int rtLockValidatorSrcPosCompare(PCRTLOCKVALSRCPOS pSrcPos1, PCRTLOCKVALSRCPOS pSrcPos2) +{ + if (pSrcPos1->uLine != pSrcPos2->uLine) + return pSrcPos1->uLine < pSrcPos2->uLine ? -1 : 1; + + int iDiff = RTStrCmp(pSrcPos1->pszFile, pSrcPos2->pszFile); + if (iDiff != 0) + return iDiff; + + iDiff = RTStrCmp(pSrcPos1->pszFunction, pSrcPos2->pszFunction); + if (iDiff != 0) + return iDiff; + + if (pSrcPos1->uId != pSrcPos2->uId) + return pSrcPos1->uId < pSrcPos2->uId ? -1 : 1; + return 0; +} + + + +/** + * Serializes destruction of RTLOCKVALREC* and RTTHREADINT structures. + */ +DECLHIDDEN(void) rtLockValidatorSerializeDestructEnter(void) +{ + RTSEMXROADS hXRoads = g_hLockValidatorXRoads; + if (hXRoads != NIL_RTSEMXROADS) + RTSemXRoadsNSEnter(hXRoads); +} + + +/** + * Call after rtLockValidatorSerializeDestructEnter. + */ +DECLHIDDEN(void) rtLockValidatorSerializeDestructLeave(void) +{ + RTSEMXROADS hXRoads = g_hLockValidatorXRoads; + if (hXRoads != NIL_RTSEMXROADS) + RTSemXRoadsNSLeave(hXRoads); +} + + +/** + * Serializes deadlock detection against destruction of the objects being + * inspected. + */ +DECLINLINE(void) rtLockValidatorSerializeDetectionEnter(void) +{ + RTSEMXROADS hXRoads = g_hLockValidatorXRoads; + if (hXRoads != NIL_RTSEMXROADS) + RTSemXRoadsEWEnter(hXRoads); +} + + +/** + * Call after rtLockValidatorSerializeDetectionEnter. + */ +DECLHIDDEN(void) rtLockValidatorSerializeDetectionLeave(void) +{ + RTSEMXROADS hXRoads = g_hLockValidatorXRoads; + if (hXRoads != NIL_RTSEMXROADS) + RTSemXRoadsEWLeave(hXRoads); +} + + +/** + * Initializes the per thread lock validator data. + * + * @param pPerThread The data. + */ +DECLHIDDEN(void) rtLockValidatorInitPerThread(RTLOCKVALPERTHREAD *pPerThread) +{ + pPerThread->bmFreeShrdOwners = UINT32_MAX; + + /* ASSUMES the rest has already been zeroed. */ + Assert(pPerThread->pRec == NULL); + Assert(pPerThread->cWriteLocks == 0); + Assert(pPerThread->cReadLocks == 0); + Assert(pPerThread->fInValidator == false); + Assert(pPerThread->pStackTop == NULL); +} + + +/** + * Delete the per thread lock validator data. + * + * @param pPerThread The data. + */ +DECLHIDDEN(void) rtLockValidatorDeletePerThread(RTLOCKVALPERTHREAD *pPerThread) +{ + /* + * Check that the thread doesn't own any locks at this time. + */ + if (pPerThread->pStackTop) + { + rtLockValComplainFirst("Thread terminating owning locks!", NULL, + RT_FROM_MEMBER(pPerThread, RTTHREADINT, LockValidator), + pPerThread->pStackTop, true); + rtLockValComplainPanic(); + } + + /* + * Free the recursion records. + */ + PRTLOCKVALRECNEST pCur = pPerThread->pFreeNestRecs; + pPerThread->pFreeNestRecs = NULL; + while (pCur) + { + PRTLOCKVALRECNEST pNext = pCur->pNextFree; + RTMemFree(pCur); + pCur = pNext; + } +} + +RTDECL(int) RTLockValidatorClassCreateEx(PRTLOCKVALCLASS phClass, PCRTLOCKVALSRCPOS pSrcPos, + bool fAutodidact, bool fRecursionOk, bool fStrictReleaseOrder, + RTMSINTERVAL cMsMinDeadlock, RTMSINTERVAL cMsMinOrder, + const char *pszNameFmt, ...) +{ + va_list va; + va_start(va, pszNameFmt); + int rc = RTLockValidatorClassCreateExV(phClass, pSrcPos, fAutodidact, fRecursionOk, fStrictReleaseOrder, + cMsMinDeadlock, cMsMinOrder, pszNameFmt, va); + va_end(va); + return rc; +} + + +RTDECL(int) RTLockValidatorClassCreateExV(PRTLOCKVALCLASS phClass, PCRTLOCKVALSRCPOS pSrcPos, + bool fAutodidact, bool fRecursionOk, bool fStrictReleaseOrder, + RTMSINTERVAL cMsMinDeadlock, RTMSINTERVAL cMsMinOrder, + const char *pszNameFmt, va_list va) +{ + Assert(cMsMinDeadlock >= 1); + Assert(cMsMinOrder >= 1); + AssertPtr(pSrcPos); + + /* + * Format the name and calc its length. + */ + size_t cbName; + char szName[32]; + if (pszNameFmt && *pszNameFmt) + cbName = RTStrPrintfV(szName, sizeof(szName), pszNameFmt, va) + 1; + else + { + static uint32_t volatile s_cAnonymous = 0; + uint32_t i = ASMAtomicIncU32(&s_cAnonymous); + cbName = RTStrPrintf(szName, sizeof(szName), "anon-%u", i - 1) + 1; + } + + /* + * Figure out the file and function name lengths and allocate memory for + * it all. + */ + size_t const cbFile = pSrcPos->pszFile ? strlen(pSrcPos->pszFile) + 1 : 0; + size_t const cbFunction = pSrcPos->pszFile ? strlen(pSrcPos->pszFunction) + 1 : 0; + RTLOCKVALCLASSINT *pThis = (RTLOCKVALCLASSINT *)RTMemAllocVarTag(sizeof(*pThis) + cbFile + cbFunction + cbName, + "may-leak:RTLockValidatorClassCreateExV"); + if (!pThis) + return VERR_NO_MEMORY; + RTMEM_MAY_LEAK(pThis); + + /* + * Initialize the class data. + */ + pThis->Core.Key = rtLockValidatorSrcPosHash(pSrcPos); + pThis->Core.uchHeight = 0; + pThis->Core.pLeft = NULL; + pThis->Core.pRight = NULL; + pThis->Core.pList = NULL; + pThis->u32Magic = RTLOCKVALCLASS_MAGIC; + pThis->cRefs = 1; + pThis->fAutodidact = fAutodidact; + pThis->fRecursionOk = fRecursionOk; + pThis->fStrictReleaseOrder = fStrictReleaseOrder; + pThis->fInTree = false; + pThis->fDonateRefToNextRetainer = false; + pThis->afReserved[0] = false; + pThis->afReserved[1] = false; + pThis->afReserved[2] = false; + pThis->cMsMinDeadlock = cMsMinDeadlock; + pThis->cMsMinOrder = cMsMinOrder; + for (unsigned i = 0; i < RT_ELEMENTS(pThis->au32Reserved); i++) + pThis->au32Reserved[i] = 0; + for (unsigned i = 0; i < RT_ELEMENTS(pThis->PriorLocks.aRefs); i++) + { + pThis->PriorLocks.aRefs[i].hClass = NIL_RTLOCKVALCLASS; + pThis->PriorLocks.aRefs[i].cLookups = 0; + pThis->PriorLocks.aRefs[i].fAutodidacticism = false; + pThis->PriorLocks.aRefs[i].afReserved[0] = false; + pThis->PriorLocks.aRefs[i].afReserved[1] = false; + pThis->PriorLocks.aRefs[i].afReserved[2] = false; + } + pThis->PriorLocks.pNext = NULL; + for (unsigned i = 0; i < RT_ELEMENTS(pThis->apPriorLocksHash); i++) + pThis->apPriorLocksHash[i] = NULL; + char *pszDst = (char *)(pThis + 1); + pThis->pszName = (char *)memcpy(pszDst, szName, cbName); + pszDst += cbName; + rtLockValidatorSrcPosCopy(&pThis->CreatePos, pSrcPos); + pThis->CreatePos.pszFile = pSrcPos->pszFile ? (char *)memcpy(pszDst, pSrcPos->pszFile, cbFile) : NULL; + pszDst += cbFile; + pThis->CreatePos.pszFunction= pSrcPos->pszFunction ? (char *)memcpy(pszDst, pSrcPos->pszFunction, cbFunction) : NULL; + Assert(rtLockValidatorSrcPosHash(&pThis->CreatePos) == pThis->Core.Key); +#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS + pThis->cHashHits = 0; + pThis->cHashMisses = 0; +#endif + + *phClass = pThis; + return VINF_SUCCESS; +} + + +RTDECL(int) RTLockValidatorClassCreate(PRTLOCKVALCLASS phClass, bool fAutodidact, RT_SRC_POS_DECL, const char *pszNameFmt, ...) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_POS_NO_ID(); + va_list va; + va_start(va, pszNameFmt); + int rc = RTLockValidatorClassCreateExV(phClass, &SrcPos, + fAutodidact, true /*fRecursionOk*/, false /*fStrictReleaseOrder*/, + 1 /*cMsMinDeadlock*/, 1 /*cMsMinOrder*/, + pszNameFmt, va); + va_end(va); + return rc; +} + + +/** + * Creates a new lock validator class with a reference that is consumed by the + * first call to RTLockValidatorClassRetain. + * + * This is tailored for use in the parameter list of a semaphore constructor. + * + * @returns Class handle with a reference that is automatically consumed by the + * first retainer. NIL_RTLOCKVALCLASS if we run into trouble. + * + * @param SRC_POS The source position where call is being made from. + * Use RT_SRC_POS when possible. Optional. + * @param pszNameFmt Class name format string, optional (NULL). Max + * length is 32 bytes. + * @param ... Format string arguments. + */ +RTDECL(RTLOCKVALCLASS) RTLockValidatorClassCreateUnique(RT_SRC_POS_DECL, const char *pszNameFmt, ...) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_POS_NO_ID(); + RTLOCKVALCLASSINT *pClass; + va_list va; + va_start(va, pszNameFmt); + int rc = RTLockValidatorClassCreateExV(&pClass, &SrcPos, + true /*fAutodidact*/, true /*fRecursionOk*/, false /*fStrictReleaseOrder*/, + 1 /*cMsMinDeadlock*/, 1 /*cMsMinOrder*/, + pszNameFmt, va); + va_end(va); + if (RT_FAILURE(rc)) + return NIL_RTLOCKVALCLASS; + ASMAtomicWriteBool(&pClass->fDonateRefToNextRetainer, true); /* see rtLockValidatorClassRetain */ + return pClass; +} + + +/** + * Internal class retainer. + * @returns The new reference count. + * @param pClass The class. + */ +DECL_FORCE_INLINE(uint32_t) rtLockValidatorClassRetain(RTLOCKVALCLASSINT *pClass) +{ + uint32_t cRefs = ASMAtomicIncU32(&pClass->cRefs); + if (cRefs > RTLOCKVALCLASS_MAX_REFS) + ASMAtomicWriteU32(&pClass->cRefs, RTLOCKVALCLASS_MAX_REFS); + else if ( cRefs == 2 + && ASMAtomicXchgBool(&pClass->fDonateRefToNextRetainer, false)) + cRefs = ASMAtomicDecU32(&pClass->cRefs); + return cRefs; +} + + +/** + * Validates and retains a lock validator class. + * + * @returns @a hClass on success, NIL_RTLOCKVALCLASS on failure. + * @param hClass The class handle. NIL_RTLOCKVALCLASS is ok. + */ +DECL_FORCE_INLINE(RTLOCKVALCLASS) rtLockValidatorClassValidateAndRetain(RTLOCKVALCLASS hClass) +{ + if (hClass == NIL_RTLOCKVALCLASS) + return hClass; + AssertPtrReturn(hClass, NIL_RTLOCKVALCLASS); + AssertReturn(hClass->u32Magic == RTLOCKVALCLASS_MAGIC, NIL_RTLOCKVALCLASS); + rtLockValidatorClassRetain(hClass); + return hClass; +} + + +/** + * Internal class releaser. + * @returns The new reference count. + * @param pClass The class. + */ +DECLINLINE(uint32_t) rtLockValidatorClassRelease(RTLOCKVALCLASSINT *pClass) +{ + uint32_t cRefs = ASMAtomicDecU32(&pClass->cRefs); + if (cRefs + 1 == RTLOCKVALCLASS_MAX_REFS) + ASMAtomicWriteU32(&pClass->cRefs, RTLOCKVALCLASS_MAX_REFS); + else if (!cRefs) + rtLockValidatorClassDestroy(pClass); + return cRefs; +} + + +/** + * Destroys a class once there are not more references to it. + * + * @param pClass The class. + */ +static void rtLockValidatorClassDestroy(RTLOCKVALCLASSINT *pClass) +{ + AssertReturnVoid(!pClass->fInTree); + ASMAtomicWriteU32(&pClass->u32Magic, RTLOCKVALCLASS_MAGIC_DEAD); + + PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; + while (pChunk) + { + for (uint32_t i = 0; i < RT_ELEMENTS(pChunk->aRefs); i++) + { + RTLOCKVALCLASSINT *pClass2 = pChunk->aRefs[i].hClass; + if (pClass2 != NIL_RTLOCKVALCLASS) + { + pChunk->aRefs[i].hClass = NIL_RTLOCKVALCLASS; + rtLockValidatorClassRelease(pClass2); + } + } + + PRTLOCKVALCLASSREFCHUNK pNext = pChunk->pNext; + pChunk->pNext = NULL; + if (pChunk != &pClass->PriorLocks) + RTMemFree(pChunk); + pChunk = pNext; + } + + RTMemFree(pClass); +} + + +RTDECL(RTLOCKVALCLASS) RTLockValidatorClassFindForSrcPos(PRTLOCKVALSRCPOS pSrcPos) +{ + if (g_hLockValClassTreeRWLock == NIL_RTSEMRW) + rtLockValidatorLazyInit(); + int rcLock = RTSemRWRequestRead(g_hLockValClassTreeRWLock, RT_INDEFINITE_WAIT); + + uint32_t uSrcPosHash = rtLockValidatorSrcPosHash(pSrcPos); + RTLOCKVALCLASSINT *pClass = (RTLOCKVALCLASSINT *)RTAvllU32Get(&g_LockValClassTree, uSrcPosHash); + while (pClass) + { + if (rtLockValidatorSrcPosCompare(&pClass->CreatePos, pSrcPos) == 0) + break; + pClass = (RTLOCKVALCLASSINT *)pClass->Core.pList; + } + + if (RT_SUCCESS(rcLock)) + RTSemRWReleaseRead(g_hLockValClassTreeRWLock); + return pClass; +} + + +RTDECL(RTLOCKVALCLASS) RTLockValidatorClassForSrcPos(RT_SRC_POS_DECL, const char *pszNameFmt, ...) +{ + RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_POS_NO_ID(); + RTLOCKVALCLASS hClass = RTLockValidatorClassFindForSrcPos(&SrcPos); + if (hClass == NIL_RTLOCKVALCLASS) + { + /* + * Create a new class and insert it into the tree. + */ + va_list va; + va_start(va, pszNameFmt); + int rc = RTLockValidatorClassCreateExV(&hClass, &SrcPos, + true /*fAutodidact*/, true /*fRecursionOk*/, false /*fStrictReleaseOrder*/, + 1 /*cMsMinDeadlock*/, 1 /*cMsMinOrder*/, + pszNameFmt, va); + va_end(va); + if (RT_SUCCESS(rc)) + { + if (g_hLockValClassTreeRWLock == NIL_RTSEMRW) + rtLockValidatorLazyInit(); + int rcLock = RTSemRWRequestWrite(g_hLockValClassTreeRWLock, RT_INDEFINITE_WAIT); + + Assert(!hClass->fInTree); + hClass->fInTree = RTAvllU32Insert(&g_LockValClassTree, &hClass->Core); + Assert(hClass->fInTree); + + if (RT_SUCCESS(rcLock)) + RTSemRWReleaseWrite(g_hLockValClassTreeRWLock); + return hClass; + } + } + return hClass; +} + + +RTDECL(uint32_t) RTLockValidatorClassRetain(RTLOCKVALCLASS hClass) +{ + RTLOCKVALCLASSINT *pClass = hClass; + AssertPtrReturn(pClass, UINT32_MAX); + AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, UINT32_MAX); + return rtLockValidatorClassRetain(pClass); +} + + +RTDECL(uint32_t) RTLockValidatorClassRelease(RTLOCKVALCLASS hClass) +{ + RTLOCKVALCLASSINT *pClass = hClass; + if (pClass == NIL_RTLOCKVALCLASS) + return 0; + AssertPtrReturn(pClass, UINT32_MAX); + AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, UINT32_MAX); + return rtLockValidatorClassRelease(pClass); +} + + +/** + * Worker for rtLockValidatorClassIsPriorClass that does a linear search thru + * all the chunks for @a pPriorClass. + * + * @returns true / false. + * @param pClass The class to search. + * @param pPriorClass The class to search for. + */ +static bool rtLockValidatorClassIsPriorClassByLinearSearch(RTLOCKVALCLASSINT *pClass, RTLOCKVALCLASSINT *pPriorClass) +{ + for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; pChunk; pChunk = pChunk->pNext) + for (uint32_t i = 0; i < RT_ELEMENTS(pChunk->aRefs); i++) + { + if (pChunk->aRefs[i].hClass == pPriorClass) + { + uint32_t cLookups = ASMAtomicIncU32(&pChunk->aRefs[i].cLookups); + if (RT_UNLIKELY(cLookups >= RTLOCKVALCLASSREF_MAX_LOOKUPS_FIX)) + { + ASMAtomicWriteU32(&pChunk->aRefs[i].cLookups, RTLOCKVALCLASSREF_MAX_LOOKUPS); + cLookups = RTLOCKVALCLASSREF_MAX_LOOKUPS; + } + + /* update the hash table entry. */ + PRTLOCKVALCLASSREF *ppHashEntry = &pClass->apPriorLocksHash[RTLOCKVALCLASS_HASH(pPriorClass)]; + if ( !(*ppHashEntry) + || (*ppHashEntry)->cLookups + 128 < cLookups) + ASMAtomicWritePtr(ppHashEntry, &pChunk->aRefs[i]); + +#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS + ASMAtomicIncU32(&pClass->cHashMisses); +#endif + return true; + } + } + + return false; +} + + +/** + * Checks if @a pPriorClass is a known prior class. + * + * @returns true / false. + * @param pClass The class to search. + * @param pPriorClass The class to search for. + */ +DECL_FORCE_INLINE(bool) rtLockValidatorClassIsPriorClass(RTLOCKVALCLASSINT *pClass, RTLOCKVALCLASSINT *pPriorClass) +{ + /* + * Hash lookup here. + */ + PRTLOCKVALCLASSREF pRef = pClass->apPriorLocksHash[RTLOCKVALCLASS_HASH(pPriorClass)]; + if ( pRef + && pRef->hClass == pPriorClass) + { + uint32_t cLookups = ASMAtomicIncU32(&pRef->cLookups); + if (RT_UNLIKELY(cLookups >= RTLOCKVALCLASSREF_MAX_LOOKUPS_FIX)) + ASMAtomicWriteU32(&pRef->cLookups, RTLOCKVALCLASSREF_MAX_LOOKUPS); +#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS + ASMAtomicIncU32(&pClass->cHashHits); +#endif + return true; + } + + return rtLockValidatorClassIsPriorClassByLinearSearch(pClass, pPriorClass); +} + + +/** + * Adds a class to the prior list. + * + * @returns VINF_SUCCESS, VERR_NO_MEMORY or VERR_SEM_LV_WRONG_ORDER. + * @param pClass The class to work on. + * @param pPriorClass The class to add. + * @param fAutodidacticism Whether we're teaching ourselves (true) or + * somebody is teaching us via the API (false). + * @param pSrcPos Where this rule was added (optional). + */ +static int rtLockValidatorClassAddPriorClass(RTLOCKVALCLASSINT *pClass, RTLOCKVALCLASSINT *pPriorClass, + bool fAutodidacticism, PCRTLOCKVALSRCPOS pSrcPos) +{ + NOREF(pSrcPos); + if (!RTCritSectIsInitialized(&g_LockValClassTeachCS)) + rtLockValidatorLazyInit(); + int rcLock = RTCritSectEnter(&g_LockValClassTeachCS); + + /* + * Check that there are no conflict (no assert since we might race each other). + */ + int rc = VERR_SEM_LV_INTERNAL_ERROR; + if (!rtLockValidatorClassIsPriorClass(pPriorClass, pClass)) + { + if (!rtLockValidatorClassIsPriorClass(pClass, pPriorClass)) + { + /* + * Scan the table for a free entry, allocating a new chunk if necessary. + */ + for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; ; pChunk = pChunk->pNext) + { + bool fDone = false; + for (uint32_t i = 0; i < RT_ELEMENTS(pChunk->aRefs); i++) + { + ASMAtomicCmpXchgHandle(&pChunk->aRefs[i].hClass, pPriorClass, NIL_RTLOCKVALCLASS, fDone); + if (fDone) + { + pChunk->aRefs[i].fAutodidacticism = fAutodidacticism; + rtLockValidatorClassRetain(pPriorClass); + rc = VINF_SUCCESS; + break; + } + } + if (fDone) + break; + + /* If no more chunks, allocate a new one and insert the class before linking it. */ + if (!pChunk->pNext) + { + PRTLOCKVALCLASSREFCHUNK pNew = (PRTLOCKVALCLASSREFCHUNK)RTMemAlloc(sizeof(*pNew)); + if (!pNew) + { + rc = VERR_NO_MEMORY; + break; + } + RTMEM_MAY_LEAK(pNew); + pNew->pNext = NULL; + for (uint32_t i = 0; i < RT_ELEMENTS(pNew->aRefs); i++) + { + pNew->aRefs[i].hClass = NIL_RTLOCKVALCLASS; + pNew->aRefs[i].cLookups = 0; + pNew->aRefs[i].fAutodidacticism = false; + pNew->aRefs[i].afReserved[0] = false; + pNew->aRefs[i].afReserved[1] = false; + pNew->aRefs[i].afReserved[2] = false; + } + + pNew->aRefs[0].hClass = pPriorClass; + pNew->aRefs[0].fAutodidacticism = fAutodidacticism; + + ASMAtomicWritePtr(&pChunk->pNext, pNew); + rtLockValidatorClassRetain(pPriorClass); + rc = VINF_SUCCESS; + break; + } + } /* chunk loop */ + } + else + rc = VINF_SUCCESS; + } + else + rc = !g_fLockValSoftWrongOrder ? VERR_SEM_LV_WRONG_ORDER : VINF_SUCCESS; + + if (RT_SUCCESS(rcLock)) + RTCritSectLeave(&g_LockValClassTeachCS); + return rc; +} + + +RTDECL(int) RTLockValidatorClassAddPriorClass(RTLOCKVALCLASS hClass, RTLOCKVALCLASS hPriorClass) +{ + RTLOCKVALCLASSINT *pClass = hClass; + AssertPtrReturn(pClass, VERR_INVALID_HANDLE); + AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_INVALID_HANDLE); + + RTLOCKVALCLASSINT *pPriorClass = hPriorClass; + AssertPtrReturn(pPriorClass, VERR_INVALID_HANDLE); + AssertReturn(pPriorClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_INVALID_HANDLE); + + return rtLockValidatorClassAddPriorClass(pClass, pPriorClass, false /*fAutodidacticism*/, NULL); +} + + +RTDECL(int) RTLockValidatorClassEnforceStrictReleaseOrder(RTLOCKVALCLASS hClass, bool fEnabled) +{ + RTLOCKVALCLASSINT *pClass = hClass; + AssertPtrReturn(pClass, VERR_INVALID_HANDLE); + AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_INVALID_HANDLE); + + ASMAtomicWriteBool(&pClass->fStrictReleaseOrder, fEnabled); + return VINF_SUCCESS; +} + + +/** + * Unlinks all siblings. + * + * This is used during record deletion and assumes no races. + * + * @param pCore One of the siblings. + */ +static void rtLockValidatorUnlinkAllSiblings(PRTLOCKVALRECCORE pCore) +{ + /* ASSUMES sibling destruction doesn't involve any races and that all + related records are to be disposed off now. */ + PRTLOCKVALRECUNION pSibling = (PRTLOCKVALRECUNION)pCore; + while (pSibling) + { + PRTLOCKVALRECUNION volatile *ppCoreNext; + switch (pSibling->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + case RTLOCKVALRECEXCL_MAGIC_DEAD: + ppCoreNext = &pSibling->Excl.pSibling; + break; + + case RTLOCKVALRECSHRD_MAGIC: + case RTLOCKVALRECSHRD_MAGIC_DEAD: + ppCoreNext = &pSibling->Shared.pSibling; + break; + + default: + AssertFailed(); + ppCoreNext = NULL; + break; + } + if (RT_UNLIKELY(ppCoreNext)) + break; + pSibling = ASMAtomicXchgPtrT(ppCoreNext, NULL, PRTLOCKVALRECUNION); + } +} + + +RTDECL(int) RTLockValidatorRecMakeSiblings(PRTLOCKVALRECCORE pRec1, PRTLOCKVALRECCORE pRec2) +{ + /* + * Validate input. + */ + PRTLOCKVALRECUNION p1 = (PRTLOCKVALRECUNION)pRec1; + PRTLOCKVALRECUNION p2 = (PRTLOCKVALRECUNION)pRec2; + + AssertPtrReturn(p1, VERR_SEM_LV_INVALID_PARAMETER); + AssertReturn( p1->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC + || p1->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC + , VERR_SEM_LV_INVALID_PARAMETER); + + AssertPtrReturn(p2, VERR_SEM_LV_INVALID_PARAMETER); + AssertReturn( p2->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC + || p2->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC + , VERR_SEM_LV_INVALID_PARAMETER); + + /* + * Link them (circular list). + */ + if ( p1->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC + && p2->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC) + { + p1->Excl.pSibling = p2; + p2->Shared.pSibling = p1; + } + else if ( p1->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC + && p2->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC) + { + p1->Shared.pSibling = p2; + p2->Excl.pSibling = p1; + } + else + AssertFailedReturn(VERR_SEM_LV_INVALID_PARAMETER); /* unsupported mix */ + + return VINF_SUCCESS; +} + + +#if 0 /* unused */ +/** + * Gets the lock name for the given record. + * + * @returns Read-only lock name. + * @param pRec The lock record. + */ +DECL_FORCE_INLINE(const char *) rtLockValidatorRecName(PRTLOCKVALRECUNION pRec) +{ + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + return pRec->Excl.szName; + case RTLOCKVALRECSHRD_MAGIC: + return pRec->Shared.szName; + case RTLOCKVALRECSHRDOWN_MAGIC: + return pRec->ShrdOwner.pSharedRec ? pRec->ShrdOwner.pSharedRec->szName : "orphaned"; + case RTLOCKVALRECNEST_MAGIC: + pRec = rtLockValidatorReadRecUnionPtr(&pRec->Nest.pRec); + if (VALID_PTR(pRec)) + { + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + return pRec->Excl.szName; + case RTLOCKVALRECSHRD_MAGIC: + return pRec->Shared.szName; + case RTLOCKVALRECSHRDOWN_MAGIC: + return pRec->ShrdOwner.pSharedRec ? pRec->ShrdOwner.pSharedRec->szName : "orphaned"; + default: + return "unknown-nested"; + } + } + return "orphaned-nested"; + default: + return "unknown"; + } +} +#endif /* unused */ + + +#if 0 /* unused */ +/** + * Gets the class for this locking record. + * + * @returns Pointer to the class or NIL_RTLOCKVALCLASS. + * @param pRec The lock validator record. + */ +DECLINLINE(RTLOCKVALCLASSINT *) rtLockValidatorRecGetClass(PRTLOCKVALRECUNION pRec) +{ + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + return pRec->Excl.hClass; + + case RTLOCKVALRECSHRD_MAGIC: + return pRec->Shared.hClass; + + case RTLOCKVALRECSHRDOWN_MAGIC: + { + PRTLOCKVALRECSHRD pSharedRec = pRec->ShrdOwner.pSharedRec; + if (RT_LIKELY( VALID_PTR(pSharedRec) + && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)) + return pSharedRec->hClass; + return NIL_RTLOCKVALCLASS; + } + + case RTLOCKVALRECNEST_MAGIC: + { + PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec; + if (VALID_PTR(pRealRec)) + { + switch (pRealRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + return pRealRec->Excl.hClass; + + case RTLOCKVALRECSHRDOWN_MAGIC: + { + PRTLOCKVALRECSHRD pSharedRec = pRealRec->ShrdOwner.pSharedRec; + if (RT_LIKELY( VALID_PTR(pSharedRec) + && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)) + return pSharedRec->hClass; + break; + } + + default: + AssertMsgFailed(("%p %p %#x\n", pRec, pRealRec, pRealRec->Core.u32Magic)); + break; + } + } + return NIL_RTLOCKVALCLASS; + } + + default: + AssertMsgFailed(("%#x\n", pRec->Core.u32Magic)); + return NIL_RTLOCKVALCLASS; + } +} +#endif /* unused */ + +/** + * Gets the class for this locking record and the pointer to the one below it in + * the stack. + * + * @returns Pointer to the class or NIL_RTLOCKVALCLASS. + * @param pRec The lock validator record. + * @param puSubClass Where to return the sub-class. + * @param ppDown Where to return the pointer to the record below. + */ +DECL_FORCE_INLINE(RTLOCKVALCLASSINT *) +rtLockValidatorRecGetClassesAndDown(PRTLOCKVALRECUNION pRec, uint32_t *puSubClass, PRTLOCKVALRECUNION *ppDown) +{ + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + *ppDown = pRec->Excl.pDown; + *puSubClass = pRec->Excl.uSubClass; + return pRec->Excl.hClass; + + case RTLOCKVALRECSHRD_MAGIC: + *ppDown = NULL; + *puSubClass = pRec->Shared.uSubClass; + return pRec->Shared.hClass; + + case RTLOCKVALRECSHRDOWN_MAGIC: + { + *ppDown = pRec->ShrdOwner.pDown; + + PRTLOCKVALRECSHRD pSharedRec = pRec->ShrdOwner.pSharedRec; + if (RT_LIKELY( VALID_PTR(pSharedRec) + && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)) + { + *puSubClass = pSharedRec->uSubClass; + return pSharedRec->hClass; + } + *puSubClass = RTLOCKVAL_SUB_CLASS_NONE; + return NIL_RTLOCKVALCLASS; + } + + case RTLOCKVALRECNEST_MAGIC: + { + *ppDown = pRec->Nest.pDown; + + PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec; + if (VALID_PTR(pRealRec)) + { + switch (pRealRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + *puSubClass = pRealRec->Excl.uSubClass; + return pRealRec->Excl.hClass; + + case RTLOCKVALRECSHRDOWN_MAGIC: + { + PRTLOCKVALRECSHRD pSharedRec = pRealRec->ShrdOwner.pSharedRec; + if (RT_LIKELY( VALID_PTR(pSharedRec) + && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)) + { + *puSubClass = pSharedRec->uSubClass; + return pSharedRec->hClass; + } + break; + } + + default: + AssertMsgFailed(("%p %p %#x\n", pRec, pRealRec, pRealRec->Core.u32Magic)); + break; + } + } + *puSubClass = RTLOCKVAL_SUB_CLASS_NONE; + return NIL_RTLOCKVALCLASS; + } + + default: + AssertMsgFailed(("%#x\n", pRec->Core.u32Magic)); + *ppDown = NULL; + *puSubClass = RTLOCKVAL_SUB_CLASS_NONE; + return NIL_RTLOCKVALCLASS; + } +} + + +/** + * Gets the sub-class for a lock record. + * + * @returns the sub-class. + * @param pRec The lock validator record. + */ +DECLINLINE(uint32_t) rtLockValidatorRecGetSubClass(PRTLOCKVALRECUNION pRec) +{ + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + return pRec->Excl.uSubClass; + + case RTLOCKVALRECSHRD_MAGIC: + return pRec->Shared.uSubClass; + + case RTLOCKVALRECSHRDOWN_MAGIC: + { + PRTLOCKVALRECSHRD pSharedRec = pRec->ShrdOwner.pSharedRec; + if (RT_LIKELY( VALID_PTR(pSharedRec) + && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)) + return pSharedRec->uSubClass; + return RTLOCKVAL_SUB_CLASS_NONE; + } + + case RTLOCKVALRECNEST_MAGIC: + { + PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec; + if (VALID_PTR(pRealRec)) + { + switch (pRealRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + return pRec->Excl.uSubClass; + + case RTLOCKVALRECSHRDOWN_MAGIC: + { + PRTLOCKVALRECSHRD pSharedRec = pRealRec->ShrdOwner.pSharedRec; + if (RT_LIKELY( VALID_PTR(pSharedRec) + && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)) + return pSharedRec->uSubClass; + break; + } + + default: + AssertMsgFailed(("%p %p %#x\n", pRec, pRealRec, pRealRec->Core.u32Magic)); + break; + } + } + return RTLOCKVAL_SUB_CLASS_NONE; + } + + default: + AssertMsgFailed(("%#x\n", pRec->Core.u32Magic)); + return RTLOCKVAL_SUB_CLASS_NONE; + } +} + + + + +/** + * Calculates the depth of a lock stack. + * + * @returns Number of stack frames. + * @param pThread The thread. + */ +static uint32_t rtLockValidatorStackDepth(PRTTHREADINT pThread) +{ + uint32_t cEntries = 0; + PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop); + while (VALID_PTR(pCur)) + { + switch (pCur->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown); + break; + + case RTLOCKVALRECSHRDOWN_MAGIC: + pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown); + break; + + case RTLOCKVALRECNEST_MAGIC: + pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown); + break; + + default: + AssertMsgFailedReturn(("%#x\n", pCur->Core.u32Magic), cEntries); + } + cEntries++; + } + return cEntries; +} + + +#ifdef RT_STRICT +/** + * Checks if the stack contains @a pRec. + * + * @returns true / false. + * @param pThreadSelf The current thread. + * @param pRec The lock record. + */ +static bool rtLockValidatorStackContainsRec(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec) +{ + PRTLOCKVALRECUNION pCur = pThreadSelf->LockValidator.pStackTop; + while (pCur) + { + AssertPtrReturn(pCur, false); + if (pCur == pRec) + return true; + switch (pCur->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + Assert(pCur->Excl.cRecursion >= 1); + pCur = pCur->Excl.pDown; + break; + + case RTLOCKVALRECSHRDOWN_MAGIC: + Assert(pCur->ShrdOwner.cRecursion >= 1); + pCur = pCur->ShrdOwner.pDown; + break; + + case RTLOCKVALRECNEST_MAGIC: + Assert(pCur->Nest.cRecursion > 1); + pCur = pCur->Nest.pDown; + break; + + default: + AssertMsgFailedReturn(("%#x\n", pCur->Core.u32Magic), false); + } + } + return false; +} +#endif /* RT_STRICT */ + + +/** + * Pushes a lock record onto the stack. + * + * @param pThreadSelf The current thread. + * @param pRec The lock record. + */ +static void rtLockValidatorStackPush(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec) +{ + Assert(pThreadSelf == RTThreadSelf()); + Assert(!rtLockValidatorStackContainsRec(pThreadSelf, pRec)); + + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + Assert(pRec->Excl.cRecursion == 1); + Assert(pRec->Excl.pDown == NULL); + rtLockValidatorWriteRecUnionPtr(&pRec->Excl.pDown, pThreadSelf->LockValidator.pStackTop); + break; + + case RTLOCKVALRECSHRDOWN_MAGIC: + Assert(pRec->ShrdOwner.cRecursion == 1); + Assert(pRec->ShrdOwner.pDown == NULL); + rtLockValidatorWriteRecUnionPtr(&pRec->ShrdOwner.pDown, pThreadSelf->LockValidator.pStackTop); + break; + + default: + AssertMsgFailedReturnVoid(("%#x\n", pRec->Core.u32Magic)); + } + rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, pRec); +} + + +/** + * Pops a lock record off the stack. + * + * @param pThreadSelf The current thread. + * @param pRec The lock. + */ +static void rtLockValidatorStackPop(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec) +{ + Assert(pThreadSelf == RTThreadSelf()); + + PRTLOCKVALRECUNION pDown; + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + Assert(pRec->Excl.cRecursion == 0); + pDown = pRec->Excl.pDown; + rtLockValidatorWriteRecUnionPtr(&pRec->Excl.pDown, NULL); /* lazy bird */ + break; + + case RTLOCKVALRECSHRDOWN_MAGIC: + Assert(pRec->ShrdOwner.cRecursion == 0); + pDown = pRec->ShrdOwner.pDown; + rtLockValidatorWriteRecUnionPtr(&pRec->ShrdOwner.pDown, NULL); + break; + + default: + AssertMsgFailedReturnVoid(("%#x\n", pRec->Core.u32Magic)); + } + if (pThreadSelf->LockValidator.pStackTop == pRec) + rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, pDown); + else + { + /* Find the pointer to our record and unlink ourselves. */ + PRTLOCKVALRECUNION pCur = pThreadSelf->LockValidator.pStackTop; + while (pCur) + { + PRTLOCKVALRECUNION volatile *ppDown; + switch (pCur->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + Assert(pCur->Excl.cRecursion >= 1); + ppDown = &pCur->Excl.pDown; + break; + + case RTLOCKVALRECSHRDOWN_MAGIC: + Assert(pCur->ShrdOwner.cRecursion >= 1); + ppDown = &pCur->ShrdOwner.pDown; + break; + + case RTLOCKVALRECNEST_MAGIC: + Assert(pCur->Nest.cRecursion >= 1); + ppDown = &pCur->Nest.pDown; + break; + + default: + AssertMsgFailedReturnVoid(("%#x\n", pCur->Core.u32Magic)); + } + pCur = *ppDown; + if (pCur == pRec) + { + rtLockValidatorWriteRecUnionPtr(ppDown, pDown); + return; + } + } + AssertMsgFailed(("%p %p\n", pRec, pThreadSelf)); + } +} + + +/** + * Creates and pushes lock recursion record onto the stack. + * + * @param pThreadSelf The current thread. + * @param pRec The lock record. + * @param pSrcPos Where the recursion occurred. + */ +static void rtLockValidatorStackPushRecursion(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec, PCRTLOCKVALSRCPOS pSrcPos) +{ + Assert(pThreadSelf == RTThreadSelf()); + Assert(rtLockValidatorStackContainsRec(pThreadSelf, pRec)); + +#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS + /* + * Allocate a new recursion record + */ + PRTLOCKVALRECNEST pRecursionRec = pThreadSelf->LockValidator.pFreeNestRecs; + if (pRecursionRec) + pThreadSelf->LockValidator.pFreeNestRecs = pRecursionRec->pNextFree; + else + { + pRecursionRec = (PRTLOCKVALRECNEST)RTMemAlloc(sizeof(*pRecursionRec)); + if (!pRecursionRec) + return; + } + + /* + * Initialize it. + */ + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + pRecursionRec->cRecursion = pRec->Excl.cRecursion; + break; + + case RTLOCKVALRECSHRDOWN_MAGIC: + pRecursionRec->cRecursion = pRec->ShrdOwner.cRecursion; + break; + + default: + AssertMsgFailed(("%#x\n", pRec->Core.u32Magic)); + rtLockValidatorSerializeDestructEnter(); + rtLockValidatorSerializeDestructLeave(); + RTMemFree(pRecursionRec); + return; + } + Assert(pRecursionRec->cRecursion > 1); + pRecursionRec->pRec = pRec; + pRecursionRec->pDown = NULL; + pRecursionRec->pNextFree = NULL; + rtLockValidatorSrcPosCopy(&pRecursionRec->SrcPos, pSrcPos); + pRecursionRec->Core.u32Magic = RTLOCKVALRECNEST_MAGIC; + + /* + * Link it. + */ + pRecursionRec->pDown = pThreadSelf->LockValidator.pStackTop; + rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, (PRTLOCKVALRECUNION)pRecursionRec); +#endif /* RTLOCKVAL_WITH_RECURSION_RECORDS */ +} + + +/** + * Pops a lock recursion record off the stack. + * + * @param pThreadSelf The current thread. + * @param pRec The lock record. + */ +static void rtLockValidatorStackPopRecursion(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec) +{ + Assert(pThreadSelf == RTThreadSelf()); + Assert(rtLockValidatorStackContainsRec(pThreadSelf, pRec)); + + uint32_t cRecursion; + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: cRecursion = pRec->Excl.cRecursion; break; + case RTLOCKVALRECSHRDOWN_MAGIC: cRecursion = pRec->ShrdOwner.cRecursion; break; + default: AssertMsgFailedReturnVoid(("%#x\n", pRec->Core.u32Magic)); + } + Assert(cRecursion >= 1); + +#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS + /* + * Pop the recursion record. + */ + PRTLOCKVALRECUNION pNest = pThreadSelf->LockValidator.pStackTop; + if ( pNest != NULL + && pNest->Core.u32Magic == RTLOCKVALRECNEST_MAGIC + && pNest->Nest.pRec == pRec + ) + { + Assert(pNest->Nest.cRecursion == cRecursion + 1); + rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, pNest->Nest.pDown); + } + else + { + /* Find the record above ours. */ + PRTLOCKVALRECUNION volatile *ppDown = NULL; + for (;;) + { + AssertMsgReturnVoid(pNest, ("%p %p\n", pRec, pThreadSelf)); + switch (pNest->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + ppDown = &pNest->Excl.pDown; + pNest = *ppDown; + continue; + case RTLOCKVALRECSHRDOWN_MAGIC: + ppDown = &pNest->ShrdOwner.pDown; + pNest = *ppDown; + continue; + case RTLOCKVALRECNEST_MAGIC: + if (pNest->Nest.pRec == pRec) + break; + ppDown = &pNest->Nest.pDown; + pNest = *ppDown; + continue; + default: + AssertMsgFailedReturnVoid(("%#x\n", pNest->Core.u32Magic)); + } + break; /* ugly */ + } + Assert(pNest->Nest.cRecursion == cRecursion + 1); + rtLockValidatorWriteRecUnionPtr(ppDown, pNest->Nest.pDown); + } + + /* + * Invalidate and free the record. + */ + ASMAtomicWriteU32(&pNest->Core.u32Magic, RTLOCKVALRECNEST_MAGIC); + rtLockValidatorWriteRecUnionPtr(&pNest->Nest.pDown, NULL); + rtLockValidatorWriteRecUnionPtr(&pNest->Nest.pRec, NULL); + pNest->Nest.cRecursion = 0; + pNest->Nest.pNextFree = pThreadSelf->LockValidator.pFreeNestRecs; + pThreadSelf->LockValidator.pFreeNestRecs = &pNest->Nest; +#endif /* RTLOCKVAL_WITH_RECURSION_RECORDS */ +} + + +/** + * Helper for rtLockValidatorStackCheckLockingOrder that does the bitching and + * returns VERR_SEM_LV_WRONG_ORDER. + */ +static int rtLockValidatorStackWrongOrder(const char *pszWhat, PCRTLOCKVALSRCPOS pSrcPos, PRTTHREADINT pThreadSelf, + PRTLOCKVALRECUNION pRec1, PRTLOCKVALRECUNION pRec2, + RTLOCKVALCLASSINT *pClass1, RTLOCKVALCLASSINT *pClass2) + + +{ + rtLockValComplainFirst(pszWhat, pSrcPos, pThreadSelf, pRec1, false); + rtLockValComplainAboutLock("Other lock: ", pRec2, "\n"); + rtLockValComplainAboutClass("My class: ", pClass1, rtLockValidatorRecGetSubClass(pRec1), true /*fVerbose*/); + rtLockValComplainAboutClass("Other class: ", pClass2, rtLockValidatorRecGetSubClass(pRec2), true /*fVerbose*/); + rtLockValComplainAboutLockStack(pThreadSelf, 0, 0, pRec2); + rtLockValComplainPanic(); + return !g_fLockValSoftWrongOrder ? VERR_SEM_LV_WRONG_ORDER : VINF_SUCCESS; +} + + +/** + * Checks if the sub-class order is ok or not. + * + * Used to deal with two locks from the same class. + * + * @returns true if ok, false if not. + * @param uSubClass1 The sub-class of the lock that is being + * considered. + * @param uSubClass2 The sub-class of the lock that is already being + * held. + */ +DECL_FORCE_INLINE(bool) rtLockValidatorIsSubClassOrderOk(uint32_t uSubClass1, uint32_t uSubClass2) +{ + if (uSubClass1 > uSubClass2) + { + /* NONE kills ANY. */ + if (uSubClass2 == RTLOCKVAL_SUB_CLASS_NONE) + return false; + return true; + } + + /* ANY counters all USER values. (uSubClass1 == NONE only if they are equal) */ + AssertCompile(RTLOCKVAL_SUB_CLASS_ANY > RTLOCKVAL_SUB_CLASS_NONE); + if (uSubClass1 == RTLOCKVAL_SUB_CLASS_ANY) + return true; + return false; +} + + +/** + * Checks if the class and sub-class lock order is ok. + * + * @returns true if ok, false if not. + * @param pClass1 The class of the lock that is being considered. + * @param uSubClass1 The sub-class that goes with @a pClass1. + * @param pClass2 The class of the lock that is already being + * held. + * @param uSubClass2 The sub-class that goes with @a pClass2. + */ +DECL_FORCE_INLINE(bool) rtLockValidatorIsClassOrderOk(RTLOCKVALCLASSINT *pClass1, uint32_t uSubClass1, + RTLOCKVALCLASSINT *pClass2, uint32_t uSubClass2) +{ + if (pClass1 == pClass2) + return rtLockValidatorIsSubClassOrderOk(uSubClass1, uSubClass2); + return rtLockValidatorClassIsPriorClass(pClass1, pClass2); +} + + +/** + * Checks the locking order, part two. + * + * @returns VINF_SUCCESS, VERR_SEM_LV_WRONG_ORDER or VERR_SEM_LV_INTERNAL_ERROR. + * @param pClass The lock class. + * @param uSubClass The lock sub-class. + * @param pThreadSelf The current thread. + * @param pRec The lock record. + * @param pSrcPos The source position of the locking operation. + * @param pFirstBadClass The first bad class. + * @param pFirstBadRec The first bad lock record. + * @param pFirstBadDown The next record on the lock stack. + */ +static int rtLockValidatorStackCheckLockingOrder2(RTLOCKVALCLASSINT * const pClass, uint32_t const uSubClass, + PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION const pRec, + PCRTLOCKVALSRCPOS const pSrcPos, + RTLOCKVALCLASSINT * const pFirstBadClass, + PRTLOCKVALRECUNION const pFirstBadRec, + PRTLOCKVALRECUNION const pFirstBadDown) +{ + /* + * Something went wrong, pCur is pointing to where. + */ + if ( pClass == pFirstBadClass + || rtLockValidatorClassIsPriorClass(pFirstBadClass, pClass)) + return rtLockValidatorStackWrongOrder("Wrong locking order!", pSrcPos, pThreadSelf, + pRec, pFirstBadRec, pClass, pFirstBadClass); + if (!pClass->fAutodidact) + return rtLockValidatorStackWrongOrder("Wrong locking order! (unknown)", pSrcPos, pThreadSelf, + pRec, pFirstBadRec, pClass, pFirstBadClass); + + /* + * This class is an autodidact, so we have to check out the rest of the stack + * for direct violations. + */ + uint32_t cNewRules = 1; + PRTLOCKVALRECUNION pCur = pFirstBadDown; + while (pCur) + { + AssertPtrReturn(pCur, VERR_SEM_LV_INTERNAL_ERROR); + + if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC) + pCur = pCur->Nest.pDown; + else + { + PRTLOCKVALRECUNION pDown; + uint32_t uPriorSubClass; + RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown); + if (pPriorClass != NIL_RTLOCKVALCLASS) + { + AssertPtrReturn(pPriorClass, VERR_SEM_LV_INTERNAL_ERROR); + AssertReturn(pPriorClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_SEM_LV_INTERNAL_ERROR); + if (!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass)) + { + if ( pClass == pPriorClass + || rtLockValidatorClassIsPriorClass(pPriorClass, pClass)) + return rtLockValidatorStackWrongOrder("Wrong locking order! (more than one)", pSrcPos, pThreadSelf, + pRec, pCur, pClass, pPriorClass); + cNewRules++; + } + } + pCur = pDown; + } + } + + if (cNewRules == 1) + { + /* + * Special case the simple operation, hoping that it will be a + * frequent case. + */ + int rc = rtLockValidatorClassAddPriorClass(pClass, pFirstBadClass, true /*fAutodidacticism*/, pSrcPos); + if (rc == VERR_SEM_LV_WRONG_ORDER) + return rtLockValidatorStackWrongOrder("Wrong locking order! (race)", pSrcPos, pThreadSelf, + pRec, pFirstBadRec, pClass, pFirstBadClass); + Assert(RT_SUCCESS(rc) || rc == VERR_NO_MEMORY); + } + else + { + /* + * We may be adding more than one rule, so we have to take the lock + * before starting to add the rules. This means we have to check + * the state after taking it since we might be racing someone adding + * a conflicting rule. + */ + if (!RTCritSectIsInitialized(&g_LockValClassTeachCS)) + rtLockValidatorLazyInit(); + int rcLock = RTCritSectEnter(&g_LockValClassTeachCS); + + /* Check */ + pCur = pFirstBadRec; + while (pCur) + { + if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC) + pCur = pCur->Nest.pDown; + else + { + uint32_t uPriorSubClass; + PRTLOCKVALRECUNION pDown; + RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown); + if (pPriorClass != NIL_RTLOCKVALCLASS) + { + if (!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass)) + { + if ( pClass == pPriorClass + || rtLockValidatorClassIsPriorClass(pPriorClass, pClass)) + { + if (RT_SUCCESS(rcLock)) + RTCritSectLeave(&g_LockValClassTeachCS); + return rtLockValidatorStackWrongOrder("Wrong locking order! (2nd)", pSrcPos, pThreadSelf, + pRec, pCur, pClass, pPriorClass); + } + } + } + pCur = pDown; + } + } + + /* Iterate the stack yet again, adding new rules this time. */ + pCur = pFirstBadRec; + while (pCur) + { + if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC) + pCur = pCur->Nest.pDown; + else + { + uint32_t uPriorSubClass; + PRTLOCKVALRECUNION pDown; + RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown); + if (pPriorClass != NIL_RTLOCKVALCLASS) + { + if (!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass)) + { + Assert( pClass != pPriorClass + && !rtLockValidatorClassIsPriorClass(pPriorClass, pClass)); + int rc = rtLockValidatorClassAddPriorClass(pClass, pPriorClass, true /*fAutodidacticism*/, pSrcPos); + if (RT_FAILURE(rc)) + { + Assert(rc == VERR_NO_MEMORY); + break; + } + Assert(rtLockValidatorClassIsPriorClass(pClass, pPriorClass)); + } + } + pCur = pDown; + } + } + + if (RT_SUCCESS(rcLock)) + RTCritSectLeave(&g_LockValClassTeachCS); + } + + return VINF_SUCCESS; +} + + + +/** + * Checks the locking order. + * + * @returns VINF_SUCCESS, VERR_SEM_LV_WRONG_ORDER or VERR_SEM_LV_INTERNAL_ERROR. + * @param pClass The lock class. + * @param uSubClass The lock sub-class. + * @param pThreadSelf The current thread. + * @param pRec The lock record. + * @param pSrcPos The source position of the locking operation. + */ +static int rtLockValidatorStackCheckLockingOrder(RTLOCKVALCLASSINT * const pClass, uint32_t const uSubClass, + PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION const pRec, + PCRTLOCKVALSRCPOS pSrcPos) +{ + /* + * Some internal paranoia first. + */ + AssertPtr(pClass); + Assert(pClass->u32Magic == RTLOCKVALCLASS_MAGIC); + AssertPtr(pThreadSelf); + Assert(pThreadSelf->u32Magic == RTTHREADINT_MAGIC); + AssertPtr(pRec); + AssertPtrNull(pSrcPos); + + /* + * Walk the stack, delegate problems to a worker routine. + */ + PRTLOCKVALRECUNION pCur = pThreadSelf->LockValidator.pStackTop; + if (!pCur) + return VINF_SUCCESS; + + for (;;) + { + AssertPtrReturn(pCur, VERR_SEM_LV_INTERNAL_ERROR); + + if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC) + pCur = pCur->Nest.pDown; + else + { + uint32_t uPriorSubClass; + PRTLOCKVALRECUNION pDown; + RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown); + if (pPriorClass != NIL_RTLOCKVALCLASS) + { + AssertPtrReturn(pPriorClass, VERR_SEM_LV_INTERNAL_ERROR); + AssertReturn(pPriorClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_SEM_LV_INTERNAL_ERROR); + if (RT_UNLIKELY(!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass))) + return rtLockValidatorStackCheckLockingOrder2(pClass, uSubClass, pThreadSelf, pRec, pSrcPos, + pPriorClass, pCur, pDown); + } + pCur = pDown; + } + if (!pCur) + return VINF_SUCCESS; + } +} + + +/** + * Check that the lock record is the topmost one on the stack, complain and fail + * if it isn't. + * + * @returns VINF_SUCCESS, VERR_SEM_LV_WRONG_RELEASE_ORDER or + * VERR_SEM_LV_INVALID_PARAMETER. + * @param pThreadSelf The current thread. + * @param pRec The record. + */ +static int rtLockValidatorStackCheckReleaseOrder(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec) +{ + AssertReturn(pThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER); + Assert(pThreadSelf == RTThreadSelf()); + + PRTLOCKVALRECUNION pTop = pThreadSelf->LockValidator.pStackTop; + if (RT_LIKELY( pTop == pRec + || ( pTop + && pTop->Core.u32Magic == RTLOCKVALRECNEST_MAGIC + && pTop->Nest.pRec == pRec) )) + return VINF_SUCCESS; + +#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS + /* Look for a recursion record so the right frame is dumped and marked. */ + while (pTop) + { + if (pTop->Core.u32Magic == RTLOCKVALRECNEST_MAGIC) + { + if (pTop->Nest.pRec == pRec) + { + pRec = pTop; + break; + } + pTop = pTop->Nest.pDown; + } + else if (pTop->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC) + pTop = pTop->Excl.pDown; + else if (pTop->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC) + pTop = pTop->ShrdOwner.pDown; + else + break; + } +#endif + + rtLockValComplainFirst("Wrong release order!", NULL, pThreadSelf, pRec, true); + rtLockValComplainPanic(); + return !g_fLockValSoftWrongOrder ? VERR_SEM_LV_WRONG_RELEASE_ORDER : VINF_SUCCESS; +} + + +/** + * Checks if all owners are blocked - shared record operated in signaller mode. + * + * @returns true / false accordingly. + * @param pRec The record. + * @param pThreadSelf The current thread. + */ +DECL_FORCE_INLINE(bool) rtLockValidatorDdAreAllThreadsBlocked(PRTLOCKVALRECSHRD pRec, PRTTHREADINT pThreadSelf) +{ + PRTLOCKVALRECSHRDOWN volatile *papOwners = pRec->papOwners; + uint32_t cAllocated = pRec->cAllocated; + uint32_t cEntries = ASMAtomicUoReadU32(&pRec->cEntries); + if (cEntries == 0) + return false; + + for (uint32_t i = 0; i < cAllocated; i++) + { + PRTLOCKVALRECSHRDOWN pEntry = rtLockValidatorUoReadSharedOwner(&papOwners[i]); + if ( pEntry + && pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC) + { + PRTTHREADINT pCurThread = rtLockValidatorReadThreadHandle(&pEntry->hThread); + if (!pCurThread) + return false; + if (pCurThread->u32Magic != RTTHREADINT_MAGIC) + return false; + if ( !RTTHREAD_IS_SLEEPING(rtThreadGetState(pCurThread)) + && pCurThread != pThreadSelf) + return false; + if (--cEntries == 0) + break; + } + else + Assert(!pEntry || pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC_DEAD); + } + + return true; +} + + +/** + * Verifies the deadlock stack before calling it a deadlock. + * + * @retval VERR_SEM_LV_DEADLOCK if it's a deadlock. + * @retval VERR_SEM_LV_ILLEGAL_UPGRADE if it's a deadlock on the same lock. + * @retval VERR_TRY_AGAIN if something changed. + * + * @param pStack The deadlock detection stack. + * @param pThreadSelf The current thread. + */ +static int rtLockValidatorDdVerifyDeadlock(PRTLOCKVALDDSTACK pStack, PRTTHREADINT pThreadSelf) +{ + uint32_t const c = pStack->c; + for (uint32_t iPass = 0; iPass < 3; iPass++) + { + for (uint32_t i = 1; i < c; i++) + { + PRTTHREADINT pThread = pStack->a[i].pThread; + if (pThread->u32Magic != RTTHREADINT_MAGIC) + return VERR_TRY_AGAIN; + if (rtThreadGetState(pThread) != pStack->a[i].enmState) + return VERR_TRY_AGAIN; + if (rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pRec) != pStack->a[i].pFirstSibling) + return VERR_TRY_AGAIN; + /* ASSUMES the signaller records won't have siblings! */ + PRTLOCKVALRECUNION pRec = pStack->a[i].pRec; + if ( pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC + && pRec->Shared.fSignaller + && !rtLockValidatorDdAreAllThreadsBlocked(&pRec->Shared, pThreadSelf)) + return VERR_TRY_AGAIN; + } + RTThreadYield(); + } + + if (c == 1) + return VERR_SEM_LV_ILLEGAL_UPGRADE; + return VERR_SEM_LV_DEADLOCK; +} + + +/** + * Checks for stack cycles caused by another deadlock before returning. + * + * @retval VINF_SUCCESS if the stack is simply too small. + * @retval VERR_SEM_LV_EXISTING_DEADLOCK if a cycle was detected. + * + * @param pStack The deadlock detection stack. + */ +static int rtLockValidatorDdHandleStackOverflow(PRTLOCKVALDDSTACK pStack) +{ + for (size_t i = 0; i < RT_ELEMENTS(pStack->a) - 1; i++) + { + PRTTHREADINT pThread = pStack->a[i].pThread; + for (size_t j = i + 1; j < RT_ELEMENTS(pStack->a); j++) + if (pStack->a[j].pThread == pThread) + return VERR_SEM_LV_EXISTING_DEADLOCK; + } + static bool volatile s_fComplained = false; + if (!s_fComplained) + { + s_fComplained = true; + rtLockValComplain(RT_SRC_POS, "lock validator stack is too small! (%zu entries)\n", RT_ELEMENTS(pStack->a)); + } + return VINF_SUCCESS; +} + + +/** + * Worker for rtLockValidatorDeadlockDetection that does the actual deadlock + * detection. + * + * @retval VINF_SUCCESS + * @retval VERR_SEM_LV_DEADLOCK + * @retval VERR_SEM_LV_EXISTING_DEADLOCK + * @retval VERR_SEM_LV_ILLEGAL_UPGRADE + * @retval VERR_TRY_AGAIN + * + * @param pStack The stack to use. + * @param pOriginalRec The original record. + * @param pThreadSelf The calling thread. + */ +static int rtLockValidatorDdDoDetection(PRTLOCKVALDDSTACK pStack, PRTLOCKVALRECUNION const pOriginalRec, + PRTTHREADINT const pThreadSelf) +{ + pStack->c = 0; + + /* We could use a single RTLOCKVALDDENTRY variable here, but the + compiler may make a better job of it when using individual variables. */ + PRTLOCKVALRECUNION pRec = pOriginalRec; + PRTLOCKVALRECUNION pFirstSibling = pOriginalRec; + uint32_t iEntry = UINT32_MAX; + PRTTHREADINT pThread = NIL_RTTHREAD; + RTTHREADSTATE enmState = RTTHREADSTATE_RUNNING; + for (uint32_t iLoop = 0; ; iLoop++) + { + /* + * Process the current record. + */ + RTLOCKVAL_ASSERT_PTR_ALIGN(pRec); + + /* Find the next relevant owner thread and record. */ + PRTLOCKVALRECUNION pNextRec = NULL; + RTTHREADSTATE enmNextState = RTTHREADSTATE_RUNNING; + PRTTHREADINT pNextThread = NIL_RTTHREAD; + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + Assert(iEntry == UINT32_MAX); + for (;;) + { + pNextThread = rtLockValidatorReadThreadHandle(&pRec->Excl.hThread); + if ( !pNextThread + || pNextThread->u32Magic != RTTHREADINT_MAGIC) + break; + enmNextState = rtThreadGetState(pNextThread); + if ( !RTTHREAD_IS_SLEEPING(enmNextState) + && pNextThread != pThreadSelf) + break; + pNextRec = rtLockValidatorReadRecUnionPtr(&pNextThread->LockValidator.pRec); + if (RT_LIKELY( !pNextRec + || enmNextState == rtThreadGetState(pNextThread))) + break; + pNextRec = NULL; + } + if (!pNextRec) + { + pRec = pRec->Excl.pSibling; + if ( pRec + && pRec != pFirstSibling) + continue; + pNextThread = NIL_RTTHREAD; + } + break; + + case RTLOCKVALRECSHRD_MAGIC: + if (!pRec->Shared.fSignaller) + { + /* Skip to the next sibling if same side. ASSUMES reader priority. */ + /** @todo The read side of a read-write lock is problematic if + * the implementation prioritizes writers over readers because + * that means we should could deadlock against current readers + * if a writer showed up. If the RW sem implementation is + * wrapping some native API, it's not so easy to detect when we + * should do this and when we shouldn't. Checking when we + * shouldn't is subject to wakeup scheduling and cannot easily + * be made reliable. + * + * At the moment we circumvent all this mess by declaring that + * readers has priority. This is TRUE on linux, but probably + * isn't on Solaris and FreeBSD. */ + if ( pRec == pFirstSibling + && pRec->Shared.pSibling != NULL + && pRec->Shared.pSibling != pFirstSibling) + { + pRec = pRec->Shared.pSibling; + Assert(iEntry == UINT32_MAX); + continue; + } + } + + /* Scan the owner table for blocked owners. */ + if ( ASMAtomicUoReadU32(&pRec->Shared.cEntries) > 0 + && ( !pRec->Shared.fSignaller + || iEntry != UINT32_MAX + || rtLockValidatorDdAreAllThreadsBlocked(&pRec->Shared, pThreadSelf) + ) + ) + { + uint32_t cAllocated = pRec->Shared.cAllocated; + PRTLOCKVALRECSHRDOWN volatile *papOwners = pRec->Shared.papOwners; + while (++iEntry < cAllocated) + { + PRTLOCKVALRECSHRDOWN pEntry = rtLockValidatorUoReadSharedOwner(&papOwners[iEntry]); + if (pEntry) + { + for (;;) + { + if (pEntry->Core.u32Magic != RTLOCKVALRECSHRDOWN_MAGIC) + break; + pNextThread = rtLockValidatorReadThreadHandle(&pEntry->hThread); + if ( !pNextThread + || pNextThread->u32Magic != RTTHREADINT_MAGIC) + break; + enmNextState = rtThreadGetState(pNextThread); + if ( !RTTHREAD_IS_SLEEPING(enmNextState) + && pNextThread != pThreadSelf) + break; + pNextRec = rtLockValidatorReadRecUnionPtr(&pNextThread->LockValidator.pRec); + if (RT_LIKELY( !pNextRec + || enmNextState == rtThreadGetState(pNextThread))) + break; + pNextRec = NULL; + } + if (pNextRec) + break; + } + else + Assert(!pEntry || pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC_DEAD); + } + if (pNextRec) + break; + pNextThread = NIL_RTTHREAD; + } + + /* Advance to the next sibling, if any. */ + pRec = pRec->Shared.pSibling; + if ( pRec != NULL + && pRec != pFirstSibling) + { + iEntry = UINT32_MAX; + continue; + } + break; + + case RTLOCKVALRECEXCL_MAGIC_DEAD: + case RTLOCKVALRECSHRD_MAGIC_DEAD: + break; + + case RTLOCKVALRECSHRDOWN_MAGIC: + case RTLOCKVALRECSHRDOWN_MAGIC_DEAD: + default: + AssertMsgFailed(("%p: %#x\n", pRec, pRec->Core)); + break; + } + + if (pNextRec) + { + /* + * Recurse and check for deadlock. + */ + uint32_t i = pStack->c; + if (RT_UNLIKELY(i >= RT_ELEMENTS(pStack->a))) + return rtLockValidatorDdHandleStackOverflow(pStack); + + pStack->c++; + pStack->a[i].pRec = pRec; + pStack->a[i].iEntry = iEntry; + pStack->a[i].enmState = enmState; + pStack->a[i].pThread = pThread; + pStack->a[i].pFirstSibling = pFirstSibling; + + if (RT_UNLIKELY( pNextThread == pThreadSelf + && ( i != 0 + || pRec->Core.u32Magic != RTLOCKVALRECSHRD_MAGIC + || !pRec->Shared.fSignaller) /* ASSUMES signaller records have no siblings. */ + ) + ) + return rtLockValidatorDdVerifyDeadlock(pStack, pThreadSelf); + + pRec = pNextRec; + pFirstSibling = pNextRec; + iEntry = UINT32_MAX; + enmState = enmNextState; + pThread = pNextThread; + } + else + { + /* + * No deadlock here, unwind the stack and deal with any unfinished + * business there. + */ + uint32_t i = pStack->c; + for (;;) + { + /* pop */ + if (i == 0) + return VINF_SUCCESS; + i--; + pRec = pStack->a[i].pRec; + iEntry = pStack->a[i].iEntry; + + /* Examine it. */ + uint32_t u32Magic = pRec->Core.u32Magic; + if (u32Magic == RTLOCKVALRECEXCL_MAGIC) + pRec = pRec->Excl.pSibling; + else if (u32Magic == RTLOCKVALRECSHRD_MAGIC) + { + if (iEntry + 1 < pRec->Shared.cAllocated) + break; /* continue processing this record. */ + pRec = pRec->Shared.pSibling; + } + else + { + Assert( u32Magic == RTLOCKVALRECEXCL_MAGIC_DEAD + || u32Magic == RTLOCKVALRECSHRD_MAGIC_DEAD); + continue; + } + + /* Any next record to advance to? */ + if ( !pRec + || pRec == pStack->a[i].pFirstSibling) + continue; + iEntry = UINT32_MAX; + break; + } + + /* Restore the rest of the state and update the stack. */ + pFirstSibling = pStack->a[i].pFirstSibling; + enmState = pStack->a[i].enmState; + pThread = pStack->a[i].pThread; + pStack->c = i; + } + + Assert(iLoop != 1000000); + } +} + + +/** + * Check for the simple no-deadlock case. + * + * @returns true if no deadlock, false if further investigation is required. + * + * @param pOriginalRec The original record. + */ +DECLINLINE(int) rtLockValidatorIsSimpleNoDeadlockCase(PRTLOCKVALRECUNION pOriginalRec) +{ + if ( pOriginalRec->Excl.Core.u32Magic == RTLOCKVALRECEXCL_MAGIC + && !pOriginalRec->Excl.pSibling) + { + PRTTHREADINT pThread = rtLockValidatorReadThreadHandle(&pOriginalRec->Excl.hThread); + if ( !pThread + || pThread->u32Magic != RTTHREADINT_MAGIC) + return true; + RTTHREADSTATE enmState = rtThreadGetState(pThread); + if (!RTTHREAD_IS_SLEEPING(enmState)) + return true; + } + return false; +} + + +/** + * Worker for rtLockValidatorDeadlockDetection that bitches about a deadlock. + * + * @param pStack The chain of locks causing the deadlock. + * @param pRec The record relating to the current thread's lock + * operation. + * @param pThreadSelf This thread. + * @param pSrcPos Where we are going to deadlock. + * @param rc The return code. + */ +static void rcLockValidatorDoDeadlockComplaining(PRTLOCKVALDDSTACK pStack, PRTLOCKVALRECUNION pRec, + PRTTHREADINT pThreadSelf, PCRTLOCKVALSRCPOS pSrcPos, int rc) +{ + if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet)) + { + const char *pszWhat; + switch (rc) + { + case VERR_SEM_LV_DEADLOCK: pszWhat = "Detected deadlock!"; break; + case VERR_SEM_LV_EXISTING_DEADLOCK: pszWhat = "Found existing deadlock!"; break; + case VERR_SEM_LV_ILLEGAL_UPGRADE: pszWhat = "Illegal lock upgrade!"; break; + default: AssertFailed(); pszWhat = "!unexpected rc!"; break; + } + rtLockValComplainFirst(pszWhat, pSrcPos, pThreadSelf, pStack->a[0].pRec != pRec ? pRec : NULL, true); + rtLockValComplainMore("---- start of deadlock chain - %u entries ----\n", pStack->c); + for (uint32_t i = 0; i < pStack->c; i++) + { + char szPrefix[24]; + RTStrPrintf(szPrefix, sizeof(szPrefix), "#%02u: ", i); + PRTLOCKVALRECUNION pShrdOwner = NULL; + if (pStack->a[i].pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC) + pShrdOwner = (PRTLOCKVALRECUNION)pStack->a[i].pRec->Shared.papOwners[pStack->a[i].iEntry]; + if (VALID_PTR(pShrdOwner) && pShrdOwner->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC) + { + rtLockValComplainAboutLock(szPrefix, pShrdOwner, "\n"); + rtLockValComplainAboutLockStack(pShrdOwner->ShrdOwner.hThread, 5, 2, pShrdOwner); + } + else + { + rtLockValComplainAboutLock(szPrefix, pStack->a[i].pRec, "\n"); + if (pStack->a[i].pRec->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC) + rtLockValComplainAboutLockStack(pStack->a[i].pRec->Excl.hThread, 5, 2, pStack->a[i].pRec); + } + } + rtLockValComplainMore("---- end of deadlock chain ----\n"); + } + + rtLockValComplainPanic(); +} + + +/** + * Perform deadlock detection. + * + * @retval VINF_SUCCESS + * @retval VERR_SEM_LV_DEADLOCK + * @retval VERR_SEM_LV_EXISTING_DEADLOCK + * @retval VERR_SEM_LV_ILLEGAL_UPGRADE + * + * @param pRec The record relating to the current thread's lock + * operation. + * @param pThreadSelf The current thread. + * @param pSrcPos The position of the current lock operation. + */ +static int rtLockValidatorDeadlockDetection(PRTLOCKVALRECUNION pRec, PRTTHREADINT pThreadSelf, PCRTLOCKVALSRCPOS pSrcPos) +{ + RTLOCKVALDDSTACK Stack; + int rc = rtLockValidatorDdDoDetection(&Stack, pRec, pThreadSelf); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + if (rc == VERR_TRY_AGAIN) + { + for (uint32_t iLoop = 0; ; iLoop++) + { + rc = rtLockValidatorDdDoDetection(&Stack, pRec, pThreadSelf); + if (RT_SUCCESS_NP(rc)) + return VINF_SUCCESS; + if (rc != VERR_TRY_AGAIN) + break; + RTThreadYield(); + if (iLoop >= 3) + return VINF_SUCCESS; + } + } + + rcLockValidatorDoDeadlockComplaining(&Stack, pRec, pThreadSelf, pSrcPos, rc); + return rc; +} + + +RTDECL(void) RTLockValidatorRecExclInitV(PRTLOCKVALRECEXCL pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass, + void *hLock, bool fEnabled, const char *pszNameFmt, va_list va) +{ + RTLOCKVAL_ASSERT_PTR_ALIGN(pRec); + RTLOCKVAL_ASSERT_PTR_ALIGN(hLock); + Assert( uSubClass >= RTLOCKVAL_SUB_CLASS_USER + || uSubClass == RTLOCKVAL_SUB_CLASS_NONE + || uSubClass == RTLOCKVAL_SUB_CLASS_ANY); + + pRec->Core.u32Magic = RTLOCKVALRECEXCL_MAGIC; + pRec->fEnabled = fEnabled && RTLockValidatorIsEnabled(); + pRec->afReserved[0] = 0; + pRec->afReserved[1] = 0; + pRec->afReserved[2] = 0; + rtLockValidatorSrcPosInit(&pRec->SrcPos); + pRec->hThread = NIL_RTTHREAD; + pRec->pDown = NULL; + pRec->hClass = rtLockValidatorClassValidateAndRetain(hClass); + pRec->uSubClass = uSubClass; + pRec->cRecursion = 0; + pRec->hLock = hLock; + pRec->pSibling = NULL; + if (pszNameFmt) + RTStrPrintfV(pRec->szName, sizeof(pRec->szName), pszNameFmt, va); + else + { + static uint32_t volatile s_cAnonymous = 0; + uint32_t i = ASMAtomicIncU32(&s_cAnonymous) - 1; + RTStrPrintf(pRec->szName, sizeof(pRec->szName), "anon-excl-%u", i); + } + + /* Lazy initialization. */ + if (RT_UNLIKELY(g_hLockValidatorXRoads == NIL_RTSEMXROADS)) + rtLockValidatorLazyInit(); +} + + +RTDECL(void) RTLockValidatorRecExclInit(PRTLOCKVALRECEXCL pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass, + void *hLock, bool fEnabled, const char *pszNameFmt, ...) +{ + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecExclInitV(pRec, hClass, uSubClass, hLock, fEnabled, pszNameFmt, va); + va_end(va); +} + + +RTDECL(int) RTLockValidatorRecExclCreateV(PRTLOCKVALRECEXCL *ppRec, RTLOCKVALCLASS hClass, + uint32_t uSubClass, void *pvLock, bool fEnabled, + const char *pszNameFmt, va_list va) +{ + PRTLOCKVALRECEXCL pRec; + *ppRec = pRec = (PRTLOCKVALRECEXCL)RTMemAlloc(sizeof(*pRec)); + if (!pRec) + return VERR_NO_MEMORY; + RTLockValidatorRecExclInitV(pRec, hClass, uSubClass, pvLock, fEnabled, pszNameFmt, va); + return VINF_SUCCESS; +} + + +RTDECL(int) RTLockValidatorRecExclCreate(PRTLOCKVALRECEXCL *ppRec, RTLOCKVALCLASS hClass, + uint32_t uSubClass, void *pvLock, bool fEnabled, + const char *pszNameFmt, ...) +{ + va_list va; + va_start(va, pszNameFmt); + int rc = RTLockValidatorRecExclCreateV(ppRec, hClass, uSubClass, pvLock, fEnabled, pszNameFmt, va); + va_end(va); + return rc; +} + + +RTDECL(void) RTLockValidatorRecExclDelete(PRTLOCKVALRECEXCL pRec) +{ + Assert(pRec->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC); + + rtLockValidatorSerializeDestructEnter(); + + /** @todo Check that it's not on our stack first. Need to make it + * configurable whether deleting a owned lock is acceptable? */ + + ASMAtomicWriteU32(&pRec->Core.u32Magic, RTLOCKVALRECEXCL_MAGIC_DEAD); + ASMAtomicWriteHandle(&pRec->hThread, NIL_RTTHREAD); + RTLOCKVALCLASS hClass; + ASMAtomicXchgHandle(&pRec->hClass, NIL_RTLOCKVALCLASS, &hClass); + if (pRec->pSibling) + rtLockValidatorUnlinkAllSiblings(&pRec->Core); + rtLockValidatorSerializeDestructLeave(); + if (hClass != NIL_RTLOCKVALCLASS) + RTLockValidatorClassRelease(hClass); +} + + +RTDECL(void) RTLockValidatorRecExclDestroy(PRTLOCKVALRECEXCL *ppRec) +{ + PRTLOCKVALRECEXCL pRec = *ppRec; + *ppRec = NULL; + if (pRec) + { + RTLockValidatorRecExclDelete(pRec); + RTMemFree(pRec); + } +} + + +RTDECL(uint32_t) RTLockValidatorRecExclSetSubClass(PRTLOCKVALRECEXCL pRec, uint32_t uSubClass) +{ + AssertPtrReturn(pRec, RTLOCKVAL_SUB_CLASS_INVALID); + AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID); + AssertReturn( uSubClass >= RTLOCKVAL_SUB_CLASS_USER + || uSubClass == RTLOCKVAL_SUB_CLASS_NONE + || uSubClass == RTLOCKVAL_SUB_CLASS_ANY, + RTLOCKVAL_SUB_CLASS_INVALID); + return ASMAtomicXchgU32(&pRec->uSubClass, uSubClass); +} + + +RTDECL(void) RTLockValidatorRecExclSetOwner(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf, + PCRTLOCKVALSRCPOS pSrcPos, bool fFirstRecursion) +{ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + if (!pRecU) + return; + AssertReturnVoid(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC); + if (!pRecU->Excl.fEnabled) + return; + if (hThreadSelf == NIL_RTTHREAD) + { + hThreadSelf = RTThreadSelfAutoAdopt(); + AssertReturnVoid(hThreadSelf != NIL_RTTHREAD); + } + AssertReturnVoid(hThreadSelf->u32Magic == RTTHREADINT_MAGIC); + Assert(hThreadSelf == RTThreadSelf()); + + ASMAtomicIncS32(&hThreadSelf->LockValidator.cWriteLocks); + + if (pRecU->Excl.hThread == hThreadSelf) + { + Assert(!fFirstRecursion); RT_NOREF_PV(fFirstRecursion); + pRecU->Excl.cRecursion++; + rtLockValidatorStackPushRecursion(hThreadSelf, pRecU, pSrcPos); + } + else + { + Assert(pRecU->Excl.hThread == NIL_RTTHREAD); + + rtLockValidatorSrcPosCopy(&pRecU->Excl.SrcPos, pSrcPos); + ASMAtomicUoWriteU32(&pRecU->Excl.cRecursion, 1); + ASMAtomicWriteHandle(&pRecU->Excl.hThread, hThreadSelf); + + rtLockValidatorStackPush(hThreadSelf, pRecU); + } +} + + +/** + * Internal worker for RTLockValidatorRecExclReleaseOwner and + * RTLockValidatorRecExclReleaseOwnerUnchecked. + */ +static void rtLockValidatorRecExclReleaseOwnerUnchecked(PRTLOCKVALRECUNION pRec, bool fFinalRecursion) +{ + RTTHREADINT *pThread = pRec->Excl.hThread; + AssertReturnVoid(pThread != NIL_RTTHREAD); + Assert(pThread == RTThreadSelf()); + + ASMAtomicDecS32(&pThread->LockValidator.cWriteLocks); + uint32_t c = ASMAtomicDecU32(&pRec->Excl.cRecursion); + if (c == 0) + { + rtLockValidatorStackPop(pThread, pRec); + ASMAtomicWriteHandle(&pRec->Excl.hThread, NIL_RTTHREAD); + } + else + { + Assert(c < UINT32_C(0xffff0000)); + Assert(!fFinalRecursion); RT_NOREF_PV(fFinalRecursion); + rtLockValidatorStackPopRecursion(pThread, pRec); + } +} + +RTDECL(int) RTLockValidatorRecExclReleaseOwner(PRTLOCKVALRECEXCL pRec, bool fFinalRecursion) +{ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + if (!pRecU) + return VINF_SUCCESS; + AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + if (!pRecU->Excl.fEnabled) + return VINF_SUCCESS; + + /* + * Check the release order. + */ + if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS + && pRecU->Excl.hClass->fStrictReleaseOrder + && pRecU->Excl.hClass->cMsMinOrder != RT_INDEFINITE_WAIT + ) + { + int rc = rtLockValidatorStackCheckReleaseOrder(pRecU->Excl.hThread, pRecU); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Join paths with RTLockValidatorRecExclReleaseOwnerUnchecked. + */ + rtLockValidatorRecExclReleaseOwnerUnchecked(pRecU, fFinalRecursion); + return VINF_SUCCESS; +} + + +RTDECL(void) RTLockValidatorRecExclReleaseOwnerUnchecked(PRTLOCKVALRECEXCL pRec) +{ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + AssertReturnVoid(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC); + if (pRecU->Excl.fEnabled) + rtLockValidatorRecExclReleaseOwnerUnchecked(pRecU, false); +} + + +RTDECL(int) RTLockValidatorRecExclRecursion(PRTLOCKVALRECEXCL pRec, PCRTLOCKVALSRCPOS pSrcPos) +{ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + if (!pRecU) + return VINF_SUCCESS; + AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + if (!pRecU->Excl.fEnabled) + return VINF_SUCCESS; + AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER); + AssertReturn(pRecU->Excl.cRecursion > 0, VERR_SEM_LV_INVALID_PARAMETER); + + if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS + && !pRecU->Excl.hClass->fRecursionOk) + { + rtLockValComplainFirst("Recursion not allowed by the class!", + pSrcPos, pRecU->Excl.hThread, (PRTLOCKVALRECUNION)pRec, true); + rtLockValComplainPanic(); + return VERR_SEM_LV_NESTED; + } + + Assert(pRecU->Excl.cRecursion < _1M); + pRecU->Excl.cRecursion++; + rtLockValidatorStackPushRecursion(pRecU->Excl.hThread, pRecU, pSrcPos); + return VINF_SUCCESS; +} + + +RTDECL(int) RTLockValidatorRecExclUnwind(PRTLOCKVALRECEXCL pRec) +{ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + if (!pRecU->Excl.fEnabled) + return VINF_SUCCESS; + AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER); + Assert(pRecU->Excl.hThread == RTThreadSelf()); + AssertReturn(pRecU->Excl.cRecursion > 1, VERR_SEM_LV_INVALID_PARAMETER); + + /* + * Check the release order. + */ + if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS + && pRecU->Excl.hClass->fStrictReleaseOrder + && pRecU->Excl.hClass->cMsMinOrder != RT_INDEFINITE_WAIT + ) + { + int rc = rtLockValidatorStackCheckReleaseOrder(pRecU->Excl.hThread, pRecU); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Perform the unwind. + */ + pRecU->Excl.cRecursion--; + rtLockValidatorStackPopRecursion(pRecU->Excl.hThread, pRecU); + return VINF_SUCCESS; +} + + +RTDECL(int) RTLockValidatorRecExclRecursionMixed(PRTLOCKVALRECEXCL pRec, PRTLOCKVALRECCORE pRecMixed, PCRTLOCKVALSRCPOS pSrcPos) +{ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + PRTLOCKVALRECUNION pRecMixedU = (PRTLOCKVALRECUNION)pRecMixed; + AssertReturn( pRecMixedU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC + || pRecMixedU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC + , VERR_SEM_LV_INVALID_PARAMETER); + if (!pRecU->Excl.fEnabled) + return VINF_SUCCESS; + Assert(pRecU->Excl.hThread == RTThreadSelf()); + AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER); + AssertReturn(pRecU->Excl.cRecursion > 0, VERR_SEM_LV_INVALID_PARAMETER); + + if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS + && !pRecU->Excl.hClass->fRecursionOk) + { + rtLockValComplainFirst("Mixed recursion not allowed by the class!", + pSrcPos, pRecU->Excl.hThread, (PRTLOCKVALRECUNION)pRec, true); + rtLockValComplainPanic(); + return VERR_SEM_LV_NESTED; + } + + Assert(pRecU->Excl.cRecursion < _1M); + pRecU->Excl.cRecursion++; + rtLockValidatorStackPushRecursion(pRecU->Excl.hThread, pRecU, pSrcPos); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTLockValidatorRecExclUnwindMixed(PRTLOCKVALRECEXCL pRec, PRTLOCKVALRECCORE pRecMixed) +{ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + PRTLOCKVALRECUNION pRecMixedU = (PRTLOCKVALRECUNION)pRecMixed; + AssertReturn( pRecMixedU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC + || pRecMixedU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC + , VERR_SEM_LV_INVALID_PARAMETER); + if (!pRecU->Excl.fEnabled) + return VINF_SUCCESS; + Assert(pRecU->Excl.hThread == RTThreadSelf()); + AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER); + AssertReturn(pRecU->Excl.cRecursion > 1, VERR_SEM_LV_INVALID_PARAMETER); + + /* + * Check the release order. + */ + if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS + && pRecU->Excl.hClass->fStrictReleaseOrder + && pRecU->Excl.hClass->cMsMinOrder != RT_INDEFINITE_WAIT + ) + { + int rc = rtLockValidatorStackCheckReleaseOrder(pRecU->Excl.hThread, pRecU); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Perform the unwind. + */ + pRecU->Excl.cRecursion--; + rtLockValidatorStackPopRecursion(pRecU->Excl.hThread, pRecU); + return VINF_SUCCESS; +} + + +RTDECL(int) RTLockValidatorRecExclCheckOrder(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf, + PCRTLOCKVALSRCPOS pSrcPos, RTMSINTERVAL cMillies) +{ + /* + * Validate and adjust input. Quit early if order validation is disabled. + */ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + if (!pRecU) + return VINF_SUCCESS; + AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + if ( !pRecU->Excl.fEnabled + || pRecU->Excl.hClass == NIL_RTLOCKVALCLASS + || pRecU->Excl.hClass->cMsMinOrder == RT_INDEFINITE_WAIT + || pRecU->Excl.hClass->cMsMinOrder > cMillies) + return VINF_SUCCESS; + + if (hThreadSelf == NIL_RTTHREAD) + { + hThreadSelf = RTThreadSelfAutoAdopt(); + AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR); + } + AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + Assert(hThreadSelf == RTThreadSelf()); + + /* + * Detect recursion as it isn't subject to order restrictions. + */ + if (pRec->hThread == hThreadSelf) + return VINF_SUCCESS; + + return rtLockValidatorStackCheckLockingOrder(pRecU->Excl.hClass, pRecU->Excl.uSubClass, hThreadSelf, pRecU, pSrcPos); +} + + +RTDECL(int) RTLockValidatorRecExclCheckBlocking(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf, + PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies, + RTTHREADSTATE enmSleepState, bool fReallySleeping) +{ + /* + * Fend off wild life. + */ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + if (!pRecU) + return VINF_SUCCESS; + AssertPtrReturn(pRecU, VERR_SEM_LV_INVALID_PARAMETER); + AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + if (!pRec->fEnabled) + return VINF_SUCCESS; + + PRTTHREADINT pThreadSelf = hThreadSelf; + AssertPtrReturn(pThreadSelf, VERR_SEM_LV_INVALID_PARAMETER); + AssertReturn(pThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + Assert(pThreadSelf == RTThreadSelf()); + + AssertReturn(RTTHREAD_IS_SLEEPING(enmSleepState), VERR_SEM_LV_INVALID_PARAMETER); + + RTTHREADSTATE enmThreadState = rtThreadGetState(pThreadSelf); + if (RT_UNLIKELY(enmThreadState != RTTHREADSTATE_RUNNING)) + { + AssertReturn( enmThreadState == RTTHREADSTATE_TERMINATED /* rtThreadRemove uses locks too */ + || enmThreadState == RTTHREADSTATE_INITIALIZING /* rtThreadInsert uses locks too */ + , VERR_SEM_LV_INVALID_PARAMETER); + enmSleepState = enmThreadState; + } + + /* + * Record the location. + */ + rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, pRecU); + rtLockValidatorSrcPosCopy(&pThreadSelf->LockValidator.SrcPos, pSrcPos); + ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, true); + pThreadSelf->LockValidator.enmRecState = enmSleepState; + rtThreadSetState(pThreadSelf, enmSleepState); + + /* + * Don't do deadlock detection if we're recursing. + * + * On some hosts we don't do recursion accounting our selves and there + * isn't any other place to check for this. + */ + int rc = VINF_SUCCESS; + if (rtLockValidatorReadThreadHandle(&pRecU->Excl.hThread) == pThreadSelf) + { + if ( !fRecursiveOk + || ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS + && !pRecU->Excl.hClass->fRecursionOk)) + { + rtLockValComplainFirst("Recursion not allowed!", pSrcPos, pThreadSelf, pRecU, true); + rtLockValComplainPanic(); + rc = VERR_SEM_LV_NESTED; + } + } + /* + * Perform deadlock detection. + */ + else if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS + && ( pRecU->Excl.hClass->cMsMinDeadlock > cMillies + || pRecU->Excl.hClass->cMsMinDeadlock > RT_INDEFINITE_WAIT)) + rc = VINF_SUCCESS; + else if (!rtLockValidatorIsSimpleNoDeadlockCase(pRecU)) + rc = rtLockValidatorDeadlockDetection(pRecU, pThreadSelf, pSrcPos); + + if (RT_SUCCESS(rc)) + ASMAtomicWriteBool(&pThreadSelf->fReallySleeping, fReallySleeping); + else + { + rtThreadSetState(pThreadSelf, enmThreadState); + rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, NULL); + } + ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, false); + return rc; +} +RT_EXPORT_SYMBOL(RTLockValidatorRecExclCheckBlocking); + + +RTDECL(int) RTLockValidatorRecExclCheckOrderAndBlocking(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf, + PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies, + RTTHREADSTATE enmSleepState, bool fReallySleeping) +{ + int rc = RTLockValidatorRecExclCheckOrder(pRec, hThreadSelf, pSrcPos, cMillies); + if (RT_SUCCESS(rc)) + rc = RTLockValidatorRecExclCheckBlocking(pRec, hThreadSelf, pSrcPos, fRecursiveOk, cMillies, + enmSleepState, fReallySleeping); + return rc; +} +RT_EXPORT_SYMBOL(RTLockValidatorRecExclCheckOrderAndBlocking); + + +RTDECL(void) RTLockValidatorRecSharedInitV(PRTLOCKVALRECSHRD pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass, + void *hLock, bool fSignaller, bool fEnabled, const char *pszNameFmt, va_list va) +{ + RTLOCKVAL_ASSERT_PTR_ALIGN(pRec); + RTLOCKVAL_ASSERT_PTR_ALIGN(hLock); + Assert( uSubClass >= RTLOCKVAL_SUB_CLASS_USER + || uSubClass == RTLOCKVAL_SUB_CLASS_NONE + || uSubClass == RTLOCKVAL_SUB_CLASS_ANY); + + pRec->Core.u32Magic = RTLOCKVALRECSHRD_MAGIC; + pRec->uSubClass = uSubClass; + pRec->hClass = rtLockValidatorClassValidateAndRetain(hClass); + pRec->hLock = hLock; + pRec->fEnabled = fEnabled && RTLockValidatorIsEnabled(); + pRec->fSignaller = fSignaller; + pRec->pSibling = NULL; + + /* the table */ + pRec->cEntries = 0; + pRec->iLastEntry = 0; + pRec->cAllocated = 0; + pRec->fReallocating = false; + pRec->fPadding = false; + pRec->papOwners = NULL; + + /* the name */ + if (pszNameFmt) + RTStrPrintfV(pRec->szName, sizeof(pRec->szName), pszNameFmt, va); + else + { + static uint32_t volatile s_cAnonymous = 0; + uint32_t i = ASMAtomicIncU32(&s_cAnonymous) - 1; + RTStrPrintf(pRec->szName, sizeof(pRec->szName), "anon-shrd-%u", i); + } +} + + +RTDECL(void) RTLockValidatorRecSharedInit(PRTLOCKVALRECSHRD pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass, + void *hLock, bool fSignaller, bool fEnabled, const char *pszNameFmt, ...) +{ + va_list va; + va_start(va, pszNameFmt); + RTLockValidatorRecSharedInitV(pRec, hClass, uSubClass, hLock, fSignaller, fEnabled, pszNameFmt, va); + va_end(va); +} + + +RTDECL(int) RTLockValidatorRecSharedCreateV(PRTLOCKVALRECSHRD *ppRec, RTLOCKVALCLASS hClass, + uint32_t uSubClass, void *pvLock, bool fSignaller, bool fEnabled, + const char *pszNameFmt, va_list va) +{ + PRTLOCKVALRECSHRD pRec; + *ppRec = pRec = (PRTLOCKVALRECSHRD)RTMemAlloc(sizeof(*pRec)); + if (!pRec) + return VERR_NO_MEMORY; + RTLockValidatorRecSharedInitV(pRec, hClass, uSubClass, pvLock, fSignaller, fEnabled, pszNameFmt, va); + return VINF_SUCCESS; +} + + +RTDECL(int) RTLockValidatorRecSharedCreate(PRTLOCKVALRECSHRD *ppRec, RTLOCKVALCLASS hClass, + uint32_t uSubClass, void *pvLock, bool fSignaller, bool fEnabled, + const char *pszNameFmt, ...) +{ + va_list va; + va_start(va, pszNameFmt); + int rc = RTLockValidatorRecSharedCreateV(ppRec, hClass, uSubClass, pvLock, fSignaller, fEnabled, pszNameFmt, va); + va_end(va); + return rc; +} + + +RTDECL(void) RTLockValidatorRecSharedDelete(PRTLOCKVALRECSHRD pRec) +{ + Assert(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC); + + /** @todo Check that it's not on our stack first. Need to make it + * configurable whether deleting a owned lock is acceptable? */ + + /* + * Flip it into table realloc mode and take the destruction lock. + */ + rtLockValidatorSerializeDestructEnter(); + while (!ASMAtomicCmpXchgBool(&pRec->fReallocating, true, false)) + { + rtLockValidatorSerializeDestructLeave(); + + rtLockValidatorSerializeDetectionEnter(); + rtLockValidatorSerializeDetectionLeave(); + + rtLockValidatorSerializeDestructEnter(); + } + + ASMAtomicWriteU32(&pRec->Core.u32Magic, RTLOCKVALRECSHRD_MAGIC_DEAD); + RTLOCKVALCLASS hClass; + ASMAtomicXchgHandle(&pRec->hClass, NIL_RTLOCKVALCLASS, &hClass); + if (pRec->papOwners) + { + PRTLOCKVALRECSHRDOWN volatile *papOwners = pRec->papOwners; + ASMAtomicUoWriteNullPtr(&pRec->papOwners); + ASMAtomicUoWriteU32(&pRec->cAllocated, 0); + + RTMemFree((void *)papOwners); + } + if (pRec->pSibling) + rtLockValidatorUnlinkAllSiblings(&pRec->Core); + ASMAtomicWriteBool(&pRec->fReallocating, false); + + rtLockValidatorSerializeDestructLeave(); + + if (hClass != NIL_RTLOCKVALCLASS) + RTLockValidatorClassRelease(hClass); +} + + +RTDECL(void) RTLockValidatorRecSharedDestroy(PRTLOCKVALRECSHRD *ppRec) +{ + PRTLOCKVALRECSHRD pRec = *ppRec; + *ppRec = NULL; + if (pRec) + { + RTLockValidatorRecSharedDelete(pRec); + RTMemFree(pRec); + } +} + + +RTDECL(uint32_t) RTLockValidatorRecSharedSetSubClass(PRTLOCKVALRECSHRD pRec, uint32_t uSubClass) +{ + AssertPtrReturn(pRec, RTLOCKVAL_SUB_CLASS_INVALID); + AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID); + AssertReturn( uSubClass >= RTLOCKVAL_SUB_CLASS_USER + || uSubClass == RTLOCKVAL_SUB_CLASS_NONE + || uSubClass == RTLOCKVAL_SUB_CLASS_ANY, + RTLOCKVAL_SUB_CLASS_INVALID); + return ASMAtomicXchgU32(&pRec->uSubClass, uSubClass); +} + + +/** + * Locates an owner (thread) in a shared lock record. + * + * @returns Pointer to the owner entry on success, NULL on failure.. + * @param pShared The shared lock record. + * @param hThread The thread (owner) to find. + * @param piEntry Where to optionally return the table in index. + * Optional. + */ +DECLINLINE(PRTLOCKVALRECUNION) +rtLockValidatorRecSharedFindOwner(PRTLOCKVALRECSHRD pShared, RTTHREAD hThread, uint32_t *piEntry) +{ + rtLockValidatorSerializeDetectionEnter(); + + PRTLOCKVALRECSHRDOWN volatile *papOwners = pShared->papOwners; + if (papOwners) + { + uint32_t const cMax = pShared->cAllocated; + for (uint32_t iEntry = 0; iEntry < cMax; iEntry++) + { + PRTLOCKVALRECUNION pEntry = (PRTLOCKVALRECUNION)rtLockValidatorUoReadSharedOwner(&papOwners[iEntry]); + if (pEntry && pEntry->ShrdOwner.hThread == hThread) + { + rtLockValidatorSerializeDetectionLeave(); + if (piEntry) + *piEntry = iEntry; + return pEntry; + } + } + } + + rtLockValidatorSerializeDetectionLeave(); + return NULL; +} + + +RTDECL(int) RTLockValidatorRecSharedCheckOrder(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf, + PCRTLOCKVALSRCPOS pSrcPos, RTMSINTERVAL cMillies) +{ + /* + * Validate and adjust input. Quit early if order validation is disabled. + */ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + if ( !pRecU->Shared.fEnabled + || pRecU->Shared.hClass == NIL_RTLOCKVALCLASS + || pRecU->Shared.hClass->cMsMinOrder == RT_INDEFINITE_WAIT + || pRecU->Shared.hClass->cMsMinOrder > cMillies + ) + return VINF_SUCCESS; + + if (hThreadSelf == NIL_RTTHREAD) + { + hThreadSelf = RTThreadSelfAutoAdopt(); + AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR); + } + AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + Assert(hThreadSelf == RTThreadSelf()); + + /* + * Detect recursion as it isn't subject to order restrictions. + */ + PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(&pRecU->Shared, hThreadSelf, NULL); + if (pEntry) + return VINF_SUCCESS; + + return rtLockValidatorStackCheckLockingOrder(pRecU->Shared.hClass, pRecU->Shared.uSubClass, hThreadSelf, pRecU, pSrcPos); +} + + +RTDECL(int) RTLockValidatorRecSharedCheckBlocking(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf, + PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies, + RTTHREADSTATE enmSleepState, bool fReallySleeping) +{ + /* + * Fend off wild life. + */ + PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec; + AssertPtrReturn(pRecU, VERR_SEM_LV_INVALID_PARAMETER); + AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + if (!pRecU->Shared.fEnabled) + return VINF_SUCCESS; + + PRTTHREADINT pThreadSelf = hThreadSelf; + AssertPtrReturn(pThreadSelf, VERR_SEM_LV_INVALID_PARAMETER); + AssertReturn(pThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + Assert(pThreadSelf == RTThreadSelf()); + + AssertReturn(RTTHREAD_IS_SLEEPING(enmSleepState), VERR_SEM_LV_INVALID_PARAMETER); + + RTTHREADSTATE enmThreadState = rtThreadGetState(pThreadSelf); + if (RT_UNLIKELY(enmThreadState != RTTHREADSTATE_RUNNING)) + { + AssertReturn( enmThreadState == RTTHREADSTATE_TERMINATED /* rtThreadRemove uses locks too */ + || enmThreadState == RTTHREADSTATE_INITIALIZING /* rtThreadInsert uses locks too */ + , VERR_SEM_LV_INVALID_PARAMETER); + enmSleepState = enmThreadState; + } + + /* + * Record the location. + */ + rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, pRecU); + rtLockValidatorSrcPosCopy(&pThreadSelf->LockValidator.SrcPos, pSrcPos); + ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, true); + pThreadSelf->LockValidator.enmRecState = enmSleepState; + rtThreadSetState(pThreadSelf, enmSleepState); + + /* + * Don't do deadlock detection if we're recursing. + */ + int rc = VINF_SUCCESS; + PRTLOCKVALRECUNION pEntry = !pRecU->Shared.fSignaller + ? rtLockValidatorRecSharedFindOwner(&pRecU->Shared, pThreadSelf, NULL) + : NULL; + if (pEntry) + { + if ( !fRecursiveOk + || ( pRec->hClass + && !pRec->hClass->fRecursionOk) + ) + { + rtLockValComplainFirst("Recursion not allowed!", pSrcPos, pThreadSelf, pRecU, true); + rtLockValComplainPanic(); + rc = VERR_SEM_LV_NESTED; + } + } + /* + * Perform deadlock detection. + */ + else if ( pRec->hClass + && ( pRec->hClass->cMsMinDeadlock == RT_INDEFINITE_WAIT + || pRec->hClass->cMsMinDeadlock > cMillies)) + rc = VINF_SUCCESS; + else if (!rtLockValidatorIsSimpleNoDeadlockCase(pRecU)) + rc = rtLockValidatorDeadlockDetection(pRecU, pThreadSelf, pSrcPos); + + if (RT_SUCCESS(rc)) + ASMAtomicWriteBool(&pThreadSelf->fReallySleeping, fReallySleeping); + else + { + rtThreadSetState(pThreadSelf, enmThreadState); + rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, NULL); + } + ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, false); + return rc; +} +RT_EXPORT_SYMBOL(RTLockValidatorRecSharedCheckBlocking); + + +RTDECL(int) RTLockValidatorRecSharedCheckOrderAndBlocking(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf, + PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies, + RTTHREADSTATE enmSleepState, bool fReallySleeping) +{ + int rc = RTLockValidatorRecSharedCheckOrder(pRec, hThreadSelf, pSrcPos, cMillies); + if (RT_SUCCESS(rc)) + rc = RTLockValidatorRecSharedCheckBlocking(pRec, hThreadSelf, pSrcPos, fRecursiveOk, cMillies, + enmSleepState, fReallySleeping); + return rc; +} +RT_EXPORT_SYMBOL(RTLockValidatorRecSharedCheckOrderAndBlocking); + + +/** + * Allocates and initializes an owner entry for the shared lock record. + * + * @returns The new owner entry. + * @param pRec The shared lock record. + * @param pThreadSelf The calling thread and owner. Used for record + * initialization and allocation. + * @param pSrcPos The source position. + */ +DECLINLINE(PRTLOCKVALRECUNION) +rtLockValidatorRecSharedAllocOwner(PRTLOCKVALRECSHRD pRec, PRTTHREADINT pThreadSelf, PCRTLOCKVALSRCPOS pSrcPos) +{ + PRTLOCKVALRECUNION pEntry; + + /* + * Check if the thread has any statically allocated records we can easily + * make use of. + */ + unsigned iEntry = ASMBitFirstSetU32(ASMAtomicUoReadU32(&pThreadSelf->LockValidator.bmFreeShrdOwners)); + if ( iEntry > 0 + && ASMAtomicBitTestAndClear(&pThreadSelf->LockValidator.bmFreeShrdOwners, iEntry - 1)) + { + pEntry = (PRTLOCKVALRECUNION)&pThreadSelf->LockValidator.aShrdOwners[iEntry - 1]; + Assert(!pEntry->ShrdOwner.fReserved); + pEntry->ShrdOwner.fStaticAlloc = true; + rtThreadGet(pThreadSelf); + } + else + { + pEntry = (PRTLOCKVALRECUNION)RTMemAlloc(sizeof(RTLOCKVALRECSHRDOWN)); + if (RT_UNLIKELY(!pEntry)) + return NULL; + pEntry->ShrdOwner.fStaticAlloc = false; + } + + pEntry->Core.u32Magic = RTLOCKVALRECSHRDOWN_MAGIC; + pEntry->ShrdOwner.cRecursion = 1; + pEntry->ShrdOwner.fReserved = true; + pEntry->ShrdOwner.hThread = pThreadSelf; + pEntry->ShrdOwner.pDown = NULL; + pEntry->ShrdOwner.pSharedRec = pRec; +#if HC_ARCH_BITS == 32 + pEntry->ShrdOwner.pvReserved = NULL; +#endif + if (pSrcPos) + pEntry->ShrdOwner.SrcPos = *pSrcPos; + else + rtLockValidatorSrcPosInit(&pEntry->ShrdOwner.SrcPos); + return pEntry; +} + + +/** + * Frees an owner entry allocated by rtLockValidatorRecSharedAllocOwner. + * + * @param pEntry The owner entry. + */ +DECLINLINE(void) rtLockValidatorRecSharedFreeOwner(PRTLOCKVALRECSHRDOWN pEntry) +{ + if (pEntry) + { + Assert(pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC); + ASMAtomicWriteU32(&pEntry->Core.u32Magic, RTLOCKVALRECSHRDOWN_MAGIC_DEAD); + + PRTTHREADINT pThread; + ASMAtomicXchgHandle(&pEntry->hThread, NIL_RTTHREAD, &pThread); + + Assert(pEntry->fReserved); + pEntry->fReserved = false; + + if (pEntry->fStaticAlloc) + { + AssertPtrReturnVoid(pThread); + AssertReturnVoid(pThread->u32Magic == RTTHREADINT_MAGIC); + + uintptr_t iEntry = pEntry - &pThread->LockValidator.aShrdOwners[0]; + AssertReleaseReturnVoid(iEntry < RT_ELEMENTS(pThread->LockValidator.aShrdOwners)); + + Assert(!ASMBitTest(&pThread->LockValidator.bmFreeShrdOwners, (int32_t)iEntry)); + ASMAtomicBitSet(&pThread->LockValidator.bmFreeShrdOwners, (int32_t)iEntry); + + rtThreadRelease(pThread); + } + else + { + rtLockValidatorSerializeDestructEnter(); + rtLockValidatorSerializeDestructLeave(); + + RTMemFree(pEntry); + } + } +} + + +/** + * Make more room in the table. + * + * @retval true on success + * @retval false if we're out of memory or running into a bad race condition + * (probably a bug somewhere). No longer holding the lock. + * + * @param pShared The shared lock record. + */ +static bool rtLockValidatorRecSharedMakeRoom(PRTLOCKVALRECSHRD pShared) +{ + for (unsigned i = 0; i < 1000; i++) + { + /* + * Switch to the other data access direction. + */ + rtLockValidatorSerializeDetectionLeave(); + if (i >= 10) + { + Assert(i != 10 && i != 100); + RTThreadSleep(i >= 100); + } + rtLockValidatorSerializeDestructEnter(); + + /* + * Try grab the privilege to reallocating the table. + */ + if ( pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC + && ASMAtomicCmpXchgBool(&pShared->fReallocating, true, false)) + { + uint32_t cAllocated = pShared->cAllocated; + if (cAllocated < pShared->cEntries) + { + /* + * Ok, still not enough space. Reallocate the table. + */ +#if 0 /** @todo enable this after making sure growing works flawlessly. */ + uint32_t cInc = RT_ALIGN_32(pShared->cEntries - cAllocated, 16); +#else + uint32_t cInc = RT_ALIGN_32(pShared->cEntries - cAllocated, 1); +#endif + PRTLOCKVALRECSHRDOWN *papOwners; + papOwners = (PRTLOCKVALRECSHRDOWN *)RTMemRealloc((void *)pShared->papOwners, + (cAllocated + cInc) * sizeof(void *)); + if (!papOwners) + { + ASMAtomicWriteBool(&pShared->fReallocating, false); + rtLockValidatorSerializeDestructLeave(); + /* RTMemRealloc will assert */ + return false; + } + + while (cInc-- > 0) + { + papOwners[cAllocated] = NULL; + cAllocated++; + } + + ASMAtomicWritePtr(&pShared->papOwners, papOwners); + ASMAtomicWriteU32(&pShared->cAllocated, cAllocated); + } + ASMAtomicWriteBool(&pShared->fReallocating, false); + } + rtLockValidatorSerializeDestructLeave(); + + rtLockValidatorSerializeDetectionEnter(); + if (RT_UNLIKELY(pShared->Core.u32Magic != RTLOCKVALRECSHRD_MAGIC)) + break; + + if (pShared->cAllocated >= pShared->cEntries) + return true; + } + + rtLockValidatorSerializeDetectionLeave(); + AssertFailed(); /* too many iterations or destroyed while racing. */ + return false; +} + + +/** + * Adds an owner entry to a shared lock record. + * + * @returns true on success, false on serious race or we're if out of memory. + * @param pShared The shared lock record. + * @param pEntry The owner entry. + */ +DECLINLINE(bool) rtLockValidatorRecSharedAddOwner(PRTLOCKVALRECSHRD pShared, PRTLOCKVALRECSHRDOWN pEntry) +{ + rtLockValidatorSerializeDetectionEnter(); + if (RT_LIKELY(pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)) /* paranoia */ + { + if ( ASMAtomicIncU32(&pShared->cEntries) > pShared->cAllocated /** @todo add fudge */ + && !rtLockValidatorRecSharedMakeRoom(pShared)) + return false; /* the worker leave the lock */ + + PRTLOCKVALRECSHRDOWN volatile *papOwners = pShared->papOwners; + uint32_t const cMax = pShared->cAllocated; + for (unsigned i = 0; i < 100; i++) + { + for (uint32_t iEntry = 0; iEntry < cMax; iEntry++) + { + if (ASMAtomicCmpXchgPtr(&papOwners[iEntry], pEntry, NULL)) + { + rtLockValidatorSerializeDetectionLeave(); + return true; + } + } + Assert(i != 25); + } + AssertFailed(); + } + rtLockValidatorSerializeDetectionLeave(); + return false; +} + + +/** + * Remove an owner entry from a shared lock record and free it. + * + * @param pShared The shared lock record. + * @param pEntry The owner entry to remove. + * @param iEntry The last known index. + */ +DECLINLINE(void) rtLockValidatorRecSharedRemoveAndFreeOwner(PRTLOCKVALRECSHRD pShared, PRTLOCKVALRECSHRDOWN pEntry, + uint32_t iEntry) +{ + /* + * Remove it from the table. + */ + rtLockValidatorSerializeDetectionEnter(); + AssertReturnVoidStmt(pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, rtLockValidatorSerializeDetectionLeave()); + if (RT_UNLIKELY( iEntry >= pShared->cAllocated + || !ASMAtomicCmpXchgPtr(&pShared->papOwners[iEntry], NULL, pEntry))) + { + /* this shouldn't happen yet... */ + AssertFailed(); + PRTLOCKVALRECSHRDOWN volatile *papOwners = pShared->papOwners; + uint32_t const cMax = pShared->cAllocated; + for (iEntry = 0; iEntry < cMax; iEntry++) + if (ASMAtomicCmpXchgPtr(&papOwners[iEntry], NULL, pEntry)) + break; + AssertReturnVoidStmt(iEntry < cMax, rtLockValidatorSerializeDetectionLeave()); + } + uint32_t cNow = ASMAtomicDecU32(&pShared->cEntries); + Assert(!(cNow & RT_BIT_32(31))); NOREF(cNow); + rtLockValidatorSerializeDetectionLeave(); + + /* + * Successfully removed, now free it. + */ + rtLockValidatorRecSharedFreeOwner(pEntry); +} + + +RTDECL(void) RTLockValidatorRecSharedResetOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread, PCRTLOCKVALSRCPOS pSrcPos) +{ + AssertReturnVoid(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC); + if (!pRec->fEnabled) + return; + AssertReturnVoid(hThread == NIL_RTTHREAD || hThread->u32Magic == RTTHREADINT_MAGIC); + AssertReturnVoid(pRec->fSignaller); + + /* + * Free all current owners. + */ + rtLockValidatorSerializeDetectionEnter(); + while (ASMAtomicUoReadU32(&pRec->cEntries) > 0) + { + AssertReturnVoidStmt(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, rtLockValidatorSerializeDetectionLeave()); + uint32_t iEntry = 0; + uint32_t cEntries = pRec->cAllocated; + PRTLOCKVALRECSHRDOWN volatile *papEntries = pRec->papOwners; + while (iEntry < cEntries) + { + PRTLOCKVALRECSHRDOWN pEntry = ASMAtomicXchgPtrT(&papEntries[iEntry], NULL, PRTLOCKVALRECSHRDOWN); + if (pEntry) + { + ASMAtomicDecU32(&pRec->cEntries); + rtLockValidatorSerializeDetectionLeave(); + + rtLockValidatorRecSharedFreeOwner(pEntry); + + rtLockValidatorSerializeDetectionEnter(); + if (ASMAtomicUoReadU32(&pRec->cEntries) == 0) + break; + cEntries = pRec->cAllocated; + papEntries = pRec->papOwners; + } + iEntry++; + } + } + rtLockValidatorSerializeDetectionLeave(); + + if (hThread != NIL_RTTHREAD) + { + /* + * Allocate a new owner entry and insert it into the table. + */ + PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedAllocOwner(pRec, hThread, pSrcPos); + if ( pEntry + && !rtLockValidatorRecSharedAddOwner(pRec, &pEntry->ShrdOwner)) + rtLockValidatorRecSharedFreeOwner(&pEntry->ShrdOwner); + } +} +RT_EXPORT_SYMBOL(RTLockValidatorRecSharedResetOwner); + + +RTDECL(void) RTLockValidatorRecSharedAddOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread, PCRTLOCKVALSRCPOS pSrcPos) +{ + AssertReturnVoid(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC); + if (!pRec->fEnabled) + return; + if (hThread == NIL_RTTHREAD) + { + hThread = RTThreadSelfAutoAdopt(); + AssertReturnVoid(hThread != NIL_RTTHREAD); + } + AssertReturnVoid(hThread->u32Magic == RTTHREADINT_MAGIC); + + /* + * Recursive? + * + * Note! This code can be optimized to try avoid scanning the table on + * insert. However, that's annoying work that makes the code big, + * so it can wait til later sometime. + */ + PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThread, NULL); + if (pEntry) + { + Assert(!pRec->fSignaller); + pEntry->ShrdOwner.cRecursion++; + rtLockValidatorStackPushRecursion(hThread, pEntry, pSrcPos); + return; + } + + /* + * Allocate a new owner entry and insert it into the table. + */ + pEntry = rtLockValidatorRecSharedAllocOwner(pRec, hThread, pSrcPos); + if (pEntry) + { + if (rtLockValidatorRecSharedAddOwner(pRec, &pEntry->ShrdOwner)) + { + if (!pRec->fSignaller) + rtLockValidatorStackPush(hThread, pEntry); + } + else + rtLockValidatorRecSharedFreeOwner(&pEntry->ShrdOwner); + } +} +RT_EXPORT_SYMBOL(RTLockValidatorRecSharedAddOwner); + + +RTDECL(void) RTLockValidatorRecSharedRemoveOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread) +{ + AssertReturnVoid(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC); + if (!pRec->fEnabled) + return; + if (hThread == NIL_RTTHREAD) + { + hThread = RTThreadSelfAutoAdopt(); + AssertReturnVoid(hThread != NIL_RTTHREAD); + } + AssertReturnVoid(hThread->u32Magic == RTTHREADINT_MAGIC); + + /* + * Find the entry hope it's a recursive one. + */ + uint32_t iEntry = UINT32_MAX; /* shuts up gcc */ + PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThread, &iEntry); + AssertReturnVoid(pEntry); + AssertReturnVoid(pEntry->ShrdOwner.cRecursion > 0); + + uint32_t c = --pEntry->ShrdOwner.cRecursion; + if (c == 0) + { + if (!pRec->fSignaller) + rtLockValidatorStackPop(hThread, (PRTLOCKVALRECUNION)pEntry); + rtLockValidatorRecSharedRemoveAndFreeOwner(pRec, &pEntry->ShrdOwner, iEntry); + } + else + { + Assert(!pRec->fSignaller); + rtLockValidatorStackPopRecursion(hThread, pEntry); + } +} +RT_EXPORT_SYMBOL(RTLockValidatorRecSharedRemoveOwner); + + +RTDECL(bool) RTLockValidatorRecSharedIsOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread) +{ + /* Validate and resolve input. */ + AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, false); + if (!pRec->fEnabled) + return false; + if (hThread == NIL_RTTHREAD) + { + hThread = RTThreadSelfAutoAdopt(); + AssertReturn(hThread != NIL_RTTHREAD, false); + } + AssertReturn(hThread->u32Magic == RTTHREADINT_MAGIC, false); + + /* Do the job. */ + PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThread, NULL); + return pEntry != NULL; +} +RT_EXPORT_SYMBOL(RTLockValidatorRecSharedIsOwner); + + +RTDECL(int) RTLockValidatorRecSharedCheckAndRelease(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf) +{ + AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + if (!pRec->fEnabled) + return VINF_SUCCESS; + if (hThreadSelf == NIL_RTTHREAD) + { + hThreadSelf = RTThreadSelfAutoAdopt(); + AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR); + } + Assert(hThreadSelf == RTThreadSelf()); + AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + + /* + * Locate the entry for this thread in the table. + */ + uint32_t iEntry = 0; + PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThreadSelf, &iEntry); + if (RT_UNLIKELY(!pEntry)) + { + rtLockValComplainFirst("Not owner (shared)!", NULL, hThreadSelf, (PRTLOCKVALRECUNION)pRec, true); + rtLockValComplainPanic(); + return VERR_SEM_LV_NOT_OWNER; + } + + /* + * Check the release order. + */ + if ( pRec->hClass != NIL_RTLOCKVALCLASS + && pRec->hClass->fStrictReleaseOrder + && pRec->hClass->cMsMinOrder != RT_INDEFINITE_WAIT + ) + { + int rc = rtLockValidatorStackCheckReleaseOrder(hThreadSelf, (PRTLOCKVALRECUNION)pEntry); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Release the ownership or unwind a level of recursion. + */ + Assert(pEntry->ShrdOwner.cRecursion > 0); + uint32_t c = --pEntry->ShrdOwner.cRecursion; + if (c == 0) + { + rtLockValidatorStackPop(hThreadSelf, pEntry); + rtLockValidatorRecSharedRemoveAndFreeOwner(pRec, &pEntry->ShrdOwner, iEntry); + } + else + rtLockValidatorStackPopRecursion(hThreadSelf, pEntry); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTLockValidatorRecSharedCheckSignaller(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf) +{ + AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + if (!pRec->fEnabled) + return VINF_SUCCESS; + if (hThreadSelf == NIL_RTTHREAD) + { + hThreadSelf = RTThreadSelfAutoAdopt(); + AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR); + } + Assert(hThreadSelf == RTThreadSelf()); + AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER); + + /* + * Locate the entry for this thread in the table. + */ + uint32_t iEntry = 0; + PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThreadSelf, &iEntry); + if (RT_UNLIKELY(!pEntry)) + { + rtLockValComplainFirst("Invalid signaller!", NULL, hThreadSelf, (PRTLOCKVALRECUNION)pRec, true); + rtLockValComplainPanic(); + return VERR_SEM_LV_NOT_SIGNALLER; + } + return VINF_SUCCESS; +} + + +RTDECL(int32_t) RTLockValidatorWriteLockGetCount(RTTHREAD Thread) +{ + if (Thread == NIL_RTTHREAD) + return 0; + + PRTTHREADINT pThread = rtThreadGet(Thread); + if (!pThread) + return VERR_INVALID_HANDLE; + int32_t cWriteLocks = ASMAtomicReadS32(&pThread->LockValidator.cWriteLocks); + rtThreadRelease(pThread); + return cWriteLocks; +} +RT_EXPORT_SYMBOL(RTLockValidatorWriteLockGetCount); + + +RTDECL(void) RTLockValidatorWriteLockInc(RTTHREAD Thread) +{ + PRTTHREADINT pThread = rtThreadGet(Thread); + AssertReturnVoid(pThread); + ASMAtomicIncS32(&pThread->LockValidator.cWriteLocks); + rtThreadRelease(pThread); +} +RT_EXPORT_SYMBOL(RTLockValidatorWriteLockInc); + + +RTDECL(void) RTLockValidatorWriteLockDec(RTTHREAD Thread) +{ + PRTTHREADINT pThread = rtThreadGet(Thread); + AssertReturnVoid(pThread); + ASMAtomicDecS32(&pThread->LockValidator.cWriteLocks); + rtThreadRelease(pThread); +} +RT_EXPORT_SYMBOL(RTLockValidatorWriteLockDec); + + +RTDECL(int32_t) RTLockValidatorReadLockGetCount(RTTHREAD Thread) +{ + if (Thread == NIL_RTTHREAD) + return 0; + + PRTTHREADINT pThread = rtThreadGet(Thread); + if (!pThread) + return VERR_INVALID_HANDLE; + int32_t cReadLocks = ASMAtomicReadS32(&pThread->LockValidator.cReadLocks); + rtThreadRelease(pThread); + return cReadLocks; +} +RT_EXPORT_SYMBOL(RTLockValidatorReadLockGetCount); + + +RTDECL(void) RTLockValidatorReadLockInc(RTTHREAD Thread) +{ + PRTTHREADINT pThread = rtThreadGet(Thread); + Assert(pThread); + ASMAtomicIncS32(&pThread->LockValidator.cReadLocks); + rtThreadRelease(pThread); +} +RT_EXPORT_SYMBOL(RTLockValidatorReadLockInc); + + +RTDECL(void) RTLockValidatorReadLockDec(RTTHREAD Thread) +{ + PRTTHREADINT pThread = rtThreadGet(Thread); + Assert(pThread); + ASMAtomicDecS32(&pThread->LockValidator.cReadLocks); + rtThreadRelease(pThread); +} +RT_EXPORT_SYMBOL(RTLockValidatorReadLockDec); + + +RTDECL(void *) RTLockValidatorQueryBlocking(RTTHREAD hThread) +{ + void *pvLock = NULL; + PRTTHREADINT pThread = rtThreadGet(hThread); + if (pThread) + { + RTTHREADSTATE enmState = rtThreadGetState(pThread); + if (RTTHREAD_IS_SLEEPING(enmState)) + { + rtLockValidatorSerializeDetectionEnter(); + + enmState = rtThreadGetState(pThread); + if (RTTHREAD_IS_SLEEPING(enmState)) + { + PRTLOCKVALRECUNION pRec = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pRec); + if (pRec) + { + switch (pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + pvLock = pRec->Excl.hLock; + break; + + case RTLOCKVALRECSHRDOWN_MAGIC: + pRec = (PRTLOCKVALRECUNION)pRec->ShrdOwner.pSharedRec; + if (!pRec || pRec->Core.u32Magic != RTLOCKVALRECSHRD_MAGIC) + break; + RT_FALL_THRU(); + case RTLOCKVALRECSHRD_MAGIC: + pvLock = pRec->Shared.hLock; + break; + } + if (RTThreadGetState(pThread) != enmState) + pvLock = NULL; + } + } + + rtLockValidatorSerializeDetectionLeave(); + } + rtThreadRelease(pThread); + } + return pvLock; +} +RT_EXPORT_SYMBOL(RTLockValidatorQueryBlocking); + + +RTDECL(bool) RTLockValidatorIsBlockedThreadInValidator(RTTHREAD hThread) +{ + bool fRet = false; + PRTTHREADINT pThread = rtThreadGet(hThread); + if (pThread) + { + fRet = ASMAtomicReadBool(&pThread->LockValidator.fInValidator); + rtThreadRelease(pThread); + } + return fRet; +} +RT_EXPORT_SYMBOL(RTLockValidatorIsBlockedThreadInValidator); + + +RTDECL(bool) RTLockValidatorHoldsLocksInClass(RTTHREAD hCurrentThread, RTLOCKVALCLASS hClass) +{ + bool fRet = false; + if (hCurrentThread == NIL_RTTHREAD) + hCurrentThread = RTThreadSelf(); + else + Assert(hCurrentThread == RTThreadSelf()); + PRTTHREADINT pThread = rtThreadGet(hCurrentThread); + if (pThread) + { + if (hClass != NIL_RTLOCKVALCLASS) + { + PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop); + while (VALID_PTR(pCur) && !fRet) + { + switch (pCur->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + fRet = pCur->Excl.hClass == hClass; + pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown); + break; + case RTLOCKVALRECSHRDOWN_MAGIC: + fRet = VALID_PTR(pCur->ShrdOwner.pSharedRec) + && pCur->ShrdOwner.pSharedRec->hClass == hClass; + pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown); + break; + case RTLOCKVALRECNEST_MAGIC: + switch (pCur->Nest.pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + fRet = pCur->Nest.pRec->Excl.hClass == hClass; + break; + case RTLOCKVALRECSHRDOWN_MAGIC: + fRet = VALID_PTR(pCur->ShrdOwner.pSharedRec) + && pCur->Nest.pRec->ShrdOwner.pSharedRec->hClass == hClass; + break; + } + pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown); + break; + default: + pCur = NULL; + break; + } + } + } + + rtThreadRelease(pThread); + } + return fRet; +} +RT_EXPORT_SYMBOL(RTLockValidatorHoldsLocksInClass); + + +RTDECL(bool) RTLockValidatorHoldsLocksInSubClass(RTTHREAD hCurrentThread, RTLOCKVALCLASS hClass, uint32_t uSubClass) +{ + bool fRet = false; + if (hCurrentThread == NIL_RTTHREAD) + hCurrentThread = RTThreadSelf(); + else + Assert(hCurrentThread == RTThreadSelf()); + PRTTHREADINT pThread = rtThreadGet(hCurrentThread); + if (pThread) + { + if (hClass != NIL_RTLOCKVALCLASS) + { + PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop); + while (VALID_PTR(pCur) && !fRet) + { + switch (pCur->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + fRet = pCur->Excl.hClass == hClass + && pCur->Excl.uSubClass == uSubClass; + pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown); + break; + case RTLOCKVALRECSHRDOWN_MAGIC: + fRet = VALID_PTR(pCur->ShrdOwner.pSharedRec) + && pCur->ShrdOwner.pSharedRec->hClass == hClass + && pCur->ShrdOwner.pSharedRec->uSubClass == uSubClass; + pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown); + break; + case RTLOCKVALRECNEST_MAGIC: + switch (pCur->Nest.pRec->Core.u32Magic) + { + case RTLOCKVALRECEXCL_MAGIC: + fRet = pCur->Nest.pRec->Excl.hClass == hClass + && pCur->Nest.pRec->Excl.uSubClass == uSubClass; + break; + case RTLOCKVALRECSHRDOWN_MAGIC: + fRet = VALID_PTR(pCur->ShrdOwner.pSharedRec) + && pCur->Nest.pRec->ShrdOwner.pSharedRec->hClass == hClass + && pCur->Nest.pRec->ShrdOwner.pSharedRec->uSubClass == uSubClass; + break; + } + pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown); + break; + default: + pCur = NULL; + break; + } + } + } + + rtThreadRelease(pThread); + } + return fRet; +} +RT_EXPORT_SYMBOL(RTLockValidatorHoldsLocksInClass); + + +RTDECL(bool) RTLockValidatorSetEnabled(bool fEnabled) +{ + return ASMAtomicXchgBool(&g_fLockValidatorEnabled, fEnabled); +} +RT_EXPORT_SYMBOL(RTLockValidatorSetEnabled); + + +RTDECL(bool) RTLockValidatorIsEnabled(void) +{ + return ASMAtomicUoReadBool(&g_fLockValidatorEnabled); +} +RT_EXPORT_SYMBOL(RTLockValidatorIsEnabled); + + +RTDECL(bool) RTLockValidatorSetQuiet(bool fQuiet) +{ + return ASMAtomicXchgBool(&g_fLockValidatorQuiet, fQuiet); +} +RT_EXPORT_SYMBOL(RTLockValidatorSetQuiet); + + +RTDECL(bool) RTLockValidatorIsQuiet(void) +{ + return ASMAtomicUoReadBool(&g_fLockValidatorQuiet); +} +RT_EXPORT_SYMBOL(RTLockValidatorIsQuiet); + + +RTDECL(bool) RTLockValidatorSetMayPanic(bool fMayPanic) +{ + return ASMAtomicXchgBool(&g_fLockValidatorMayPanic, fMayPanic); +} +RT_EXPORT_SYMBOL(RTLockValidatorSetMayPanic); + + +RTDECL(bool) RTLockValidatorMayPanic(void) +{ + return ASMAtomicUoReadBool(&g_fLockValidatorMayPanic); +} +RT_EXPORT_SYMBOL(RTLockValidatorMayPanic); + |