// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// ===========================================================================
// File: ListLock.h
//
//
// ===========================================================================
// This file decribes the list lock and deadlock aware list lock.
// ===========================================================================
#ifndef LISTLOCK_H
#define LISTLOCK_H
#include "vars.hpp"
#include "threads.h"
#include "crst.h"
template < typename ELEMENT >
class ListLockBase;
// This structure is used for running class init methods or JITing methods
// (m_pData points to a FunctionDesc). This class cannot have a destructor since it is used
// in function that also have EX_TRY's and the VC compiler doesn't allow classes with destructors
// to be allocated in a function that used SEH.
// @FUTURE Keep a pool of these (e.g. an array), so we don't have to allocate on the fly
// m_hInitException contains a handle to the exception thrown by the class init. This
// allows us to throw this information to the caller on subsequent class init attempts.
template < typename ELEMENT >
class ListLockEntryBase
{
friend class ListLockBase;
typedef ListLockEntryBase Entry_t;
typedef ListLockBase List_t;
typedef typename List_t::LockHolder ListLockHolder;
public:
#ifdef _DEBUG
bool Check()
{
WRAPPER_NO_CONTRACT;
return m_dwRefCount != (DWORD)-1;
}
#endif // DEBUG
DeadlockAwareLock m_deadlock;
List_t * m_pList;
ELEMENT m_data;
Crst m_Crst;
const char * m_pszDescription;
Entry_t * m_pNext;
DWORD m_dwRefCount;
HRESULT m_hrResultCode;
LOADERHANDLE m_hInitException;
PTR_LoaderAllocator m_pLoaderAllocator;
#ifdef FEATURE_CORRUPTING_EXCEPTIONS
// Field to maintain the corruption severity of the exception
CorruptionSeverity m_CorruptionSeverity;
#endif // FEATURE_CORRUPTING_EXCEPTIONS
ListLockEntryBase(List_t *pList, ELEMENT data, const char *description = NULL)
: m_deadlock(description),
m_pList(pList),
m_data(data),
m_Crst(CrstListLock,
(CrstFlags)(CRST_REENTRANCY | (pList->IsHostBreakable() ? CRST_HOST_BREAKABLE : 0))),
m_pszDescription(description),
m_pNext(NULL),
m_dwRefCount(1),
m_hrResultCode(S_FALSE),
m_hInitException(NULL),
m_pLoaderAllocator(dac_cast(nullptr))
#ifdef FEATURE_CORRUPTING_EXCEPTIONS
,
m_CorruptionSeverity(NotCorrupting)
#endif // FEATURE_CORRUPTING_EXCEPTIONS
{
WRAPPER_NO_CONTRACT;
}
virtual ~ListLockEntryBase()
{
}
DEBUG_NOINLINE void Enter()
{
WRAPPER_NO_CONTRACT;
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
m_deadlock.BeginEnterLock();
DeadlockAwareLock::BlockingLockHolder dlLock;
m_Crst.Enter();
m_deadlock.EndEnterLock();
}
BOOL CanDeadlockAwareEnter()
{
WRAPPER_NO_CONTRACT;
return m_deadlock.CanEnterLock();
}
DEBUG_NOINLINE BOOL DeadlockAwareEnter()
{
WRAPPER_NO_CONTRACT;
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
if (!m_deadlock.TryBeginEnterLock())
return FALSE;
DeadlockAwareLock::BlockingLockHolder dlLock;
m_Crst.Enter();
m_deadlock.EndEnterLock();
return TRUE;
}
DEBUG_NOINLINE void Leave()
{
WRAPPER_NO_CONTRACT;
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
m_deadlock.LeaveLock();
m_Crst.Leave();
}
static Entry_t *Find(List_t* pLock, ELEMENT data, const char *description = NULL)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
_ASSERTE(pLock->HasLock());
Entry_t *pEntry = pLock->Find(data);
if (pEntry == NULL)
{
pEntry = new Entry_t(pLock, data, description);
pLock->AddElement(pEntry);
}
else
pEntry->AddRef();
return pEntry;
};
void AddRef()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(this));
}
CONTRACTL_END;
FastInterlockIncrement((LONG*)&m_dwRefCount);
}
void Release()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(this));
}
CONTRACTL_END;
ListLockHolder lock(m_pList);
if (FastInterlockDecrement((LONG*)&m_dwRefCount) == 0)
{
// Remove from list
m_pList->Unlink(this);
delete this;
}
};
#ifdef _DEBUG
BOOL HasLock()
{
WRAPPER_NO_CONTRACT;
return(m_Crst.OwnedByCurrentThread());
}
#endif
// LockHolder holds the lock of the element, not the element itself
DEBUG_NOINLINE static void LockHolderEnter(Entry_t *pThis) PUB
{
WRAPPER_NO_CONTRACT;
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
pThis->Enter();
}
DEBUG_NOINLINE static void LockHolderLeave(Entry_t *pThis) PUB
{
WRAPPER_NO_CONTRACT;
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
pThis->Leave();
}
DEBUG_NOINLINE void FinishDeadlockAwareEnter()
{
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
DeadlockAwareLock::BlockingLockHolder dlLock;
m_Crst.Enter();
m_deadlock.EndEnterLock();
}
typedef Wrapper LockHolderBase;
class LockHolder : public LockHolderBase
{
public:
LockHolder()
: LockHolderBase(NULL, FALSE)
{
}
LockHolder(Entry_t *value, BOOL take = TRUE)
: LockHolderBase(value, take)
{
}
BOOL DeadlockAwareAcquire()
{
if (!this->m_acquired && this->m_value != NULL)
{
if (!this->m_value->m_deadlock.TryBeginEnterLock())
return FALSE;
this->m_value->FinishDeadlockAwareEnter();
this->m_acquired = TRUE;
}
return TRUE;
}
};
};
template < typename ELEMENT >
class ListLockBase
{
typedef ListLockBase List_t;
typedef ListLockEntryBase Entry_t;
protected:
CrstStatic m_Crst;
BOOL m_fInited;
BOOL m_fHostBreakable; // Lock can be broken by a host for deadlock detection
Entry_t * m_pHead;
public:
BOOL IsInitialized()
{
LIMITED_METHOD_CONTRACT;
return m_fInited;
}
inline void PreInit()
{
LIMITED_METHOD_CONTRACT;
memset(this, 0, sizeof(*this));
}
// DO NOT MAKE A CONSTRUCTOR FOR THIS CLASS - There are global instances
void Init(CrstType crstType, CrstFlags flags, BOOL fHostBreakable = FALSE)
{
WRAPPER_NO_CONTRACT;
PreInit();
m_Crst.Init(crstType, flags);
m_fInited = TRUE;
m_fHostBreakable = fHostBreakable;
}
void Destroy()
{
WRAPPER_NO_CONTRACT;
// There should not be any of these around
_ASSERTE(m_pHead == NULL || dbg_fDrasticShutdown || g_fInControlC);
if (m_fInited)
{
m_fInited = FALSE;
m_Crst.Destroy();
}
}
BOOL IsHostBreakable () const
{
LIMITED_METHOD_CONTRACT;
return m_fHostBreakable;
}
void AddElement(Entry_t* pElement)
{
WRAPPER_NO_CONTRACT;
pElement->m_pNext = m_pHead;
m_pHead = pElement;
}
DEBUG_NOINLINE void Enter()
{
CANNOT_HAVE_CONTRACT; // See below
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
#if 0 // The cleanup logic contract will cause any forbid GC state from the Crst to
// get deleted. This causes asserts from Leave. We probably should make the contract
// implementation tolerant of this pattern, or else ensure that the state the contract
// modifies is not used by any other code.
CONTRACTL
{
NOTHROW;
UNCHECKED(GC_TRIGGERS); // May trigger or not based on Crst's type
MODE_ANY;
PRECONDITION(CheckPointer(this));
}
CONTRACTL_END;
#endif
m_Crst.Enter();
}
DEBUG_NOINLINE void Leave()
{
WRAPPER_NO_CONTRACT;
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
m_Crst.Leave();
}
// Must own the lock before calling this or is ok if the debugger has
// all threads stopped
inline Entry_t *Find(ELEMENT data)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
PRECONDITION(CheckPointer(this));
#ifdef DEBUGGING_SUPPORTED
PRECONDITION(m_Crst.OwnedByCurrentThread() ||
CORDebuggerAttached()
// This condition should be true, but it is awkward to assert it because adding dbginterface.h creates lots of cycles in the includes
// It didn't seem valuable enough to refactor out a wrapper just to preserve it
/* && g_pDebugInterface->IsStopped() */);
#else
PRECONDITION(m_Crst.OwnedByCurrentThread());
#endif // DEBUGGING_SUPPORTED
}
CONTRACTL_END;
Entry_t *pSearch;
for (pSearch = m_pHead; pSearch != NULL; pSearch = pSearch->m_pNext)
{
if (pSearch->m_data == data)
return pSearch;
}
return NULL;
}
// Must own the lock before calling this!
Entry_t* Pop(BOOL unloading = FALSE)
{
LIMITED_METHOD_CONTRACT;
#ifdef _DEBUG
if(unloading == FALSE)
_ASSERTE(m_Crst.OwnedByCurrentThread());
#endif
if(m_pHead == NULL) return NULL;
Entry_t* pEntry = m_pHead;
m_pHead = m_pHead->m_pNext;
return pEntry;
}
// Must own the lock before calling this!
Entry_t* Peek()
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(m_Crst.OwnedByCurrentThread());
return m_pHead;
}
// Must own the lock before calling this!
BOOL Unlink(Entry_t *pItem)
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(m_Crst.OwnedByCurrentThread());
Entry_t *pSearch;
Entry_t *pPrev;
pPrev = NULL;
for (pSearch = m_pHead; pSearch != NULL; pSearch = pSearch->m_pNext)
{
if (pSearch == pItem)
{
if (pPrev == NULL)
m_pHead = pSearch->m_pNext;
else
pPrev->m_pNext = pSearch->m_pNext;
return TRUE;
}
pPrev = pSearch;
}
// Not found
return FALSE;
}
#ifdef _DEBUG
BOOL HasLock()
{
WRAPPER_NO_CONTRACT;
STATIC_CONTRACT_SO_TOLERANT;
return(m_Crst.OwnedByCurrentThread());
}
#endif
DEBUG_NOINLINE static void HolderEnter(List_t *pThis)
{
WRAPPER_NO_CONTRACT;
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
pThis->Enter();
}
DEBUG_NOINLINE static void HolderLeave(List_t *pThis)
{
WRAPPER_NO_CONTRACT;
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
pThis->Leave();
}
typedef Wrapper LockHolder;
};
class WaitingThreadListElement
{
public:
Thread * m_pThread;
WaitingThreadListElement * m_pNext;
};
typedef class ListLockBase ListLock;
typedef class ListLockEntryBase ListLockEntry;
// Holds the lock of the ListLock
typedef ListLock::LockHolder ListLockHolder;
// Holds the ownership of the lock element
typedef ReleaseHolder ListLockEntryHolder;
// Holds the lock of the lock element
typedef ListLockEntry::LockHolder ListLockEntryLockHolder;
#endif // LISTLOCK_H