// 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"
class ListLock;
// 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.
class ListLockEntry
{
friend class ListLock;
public:
#ifdef _DEBUG
bool Check()
{
WRAPPER_NO_CONTRACT;
return m_dwRefCount != (DWORD)-1;
}
#endif // DEBUG
DeadlockAwareLock m_deadlock;
ListLock * m_pList;
void * m_pData;
Crst m_Crst;
const char * m_pszDescription;
ListLockEntry * 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
ListLockEntry(ListLock *pList, void *pData, const char *description = NULL);
virtual ~ListLockEntry()
{
}
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 ListLockEntry *Find(ListLock* pLock, LPVOID pPointer, const char *description = NULL) DAC_EMPTY_RET(NULL);
void AddRef() DAC_EMPTY_ERR();
void Release() DAC_EMPTY_ERR();
#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(ListLockEntry *pThis) PUB
{
WRAPPER_NO_CONTRACT;
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
pThis->Enter();
}
DEBUG_NOINLINE static void LockHolderLeave(ListLockEntry *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(ListLockEntry *value, BOOL take = TRUE)
: LockHolderBase(value, take)
{
}
BOOL DeadlockAwareAcquire()
{
if (!m_acquired && m_value != NULL)
{
if (!m_value->m_deadlock.TryBeginEnterLock())
return FALSE;
m_value->FinishDeadlockAwareEnter();
m_acquired = TRUE;
}
return TRUE;
}
};
};
class ListLock
{
protected:
CrstStatic m_Crst;
BOOL m_fInited;
BOOL m_fHostBreakable; // Lock can be broken by a host for deadlock detection
ListLockEntry * 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(ListLockEntry* 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
ListLockEntry *Find(void *pData);
// Must own the lock before calling this!
ListLockEntry* Pop(BOOL unloading = FALSE)
{
LIMITED_METHOD_CONTRACT;
#ifdef _DEBUG
if(unloading == FALSE)
_ASSERTE(m_Crst.OwnedByCurrentThread());
#endif
if(m_pHead == NULL) return NULL;
ListLockEntry* pEntry = m_pHead;
m_pHead = m_pHead->m_pNext;
return pEntry;
}
// Must own the lock before calling this!
ListLockEntry* Peek()
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(m_Crst.OwnedByCurrentThread());
return m_pHead;
}
// Must own the lock before calling this!
BOOL Unlink(ListLockEntry *pItem)
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(m_Crst.OwnedByCurrentThread());
ListLockEntry *pSearch;
ListLockEntry *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(ListLock *pThis)
{
WRAPPER_NO_CONTRACT;
ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
pThis->Enter();
}
DEBUG_NOINLINE static void HolderLeave(ListLock *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;
};
// 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