| // |
| // Copyright (c) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. |
| // |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // |
| // File: |
| // cs.cpp |
| // |
| // Purpose: |
| // Implementation of critical sections |
| // |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #include "pal/thread.hpp" |
| #include "pal/cs.hpp" |
| #include "pal/malloc.hpp" |
| #include "pal/list.h" |
| #include "pal/dbgmsg.h" |
| #include "pal/init.h" |
| #include "pal/process.h" |
| |
| #include <sched.h> |
| #include <pthread.h> |
| |
| using namespace CorUnix; |
| |
| // |
| // Uncomment the following line to turn CS behavior from |
| // unfair to fair lock |
| // |
| // #define PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| |
| // |
| // Uncomment the following line to enable simple mutex based CSs |
| // Note: when MUTEX_BASED_CSS is defined, PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| // has no effect |
| // |
| // #define MUTEX_BASED_CSS |
| |
| // |
| // Important notes on critical sections layout/semantics on Unix |
| // |
| // 1) The PAL_CRITICAL_SECTION structure below must match the size of the |
| // CRITICAL_SECTION defined in pal.h. Besides the "windows part" |
| // of both the structures must be identical. |
| // 2) Both PAL_CRITICAL_SECTION and CRITICAL_SECTION currently do not match |
| // the size of the Windows' CRITICAL_SECTION. |
| // - From unmanaged code point of view, one should never make assumptions |
| // on the size and layout of the CRITICAL_SECTION structure, and anyway |
| // on Unix PAL's CRITICAL_SECTION extends the Windows one, so that some |
| // assumptions may still work. |
| // - From managed code point of view, one could try to interop directly |
| // to unmanaged critical sections APIs (though that would be quite |
| // meaningless). In order to do that, she would need to define a copy |
| // of the CRITICAL_SECTION structure in his/her code, and that may lead |
| // to access random data beyond the structure limit, if that managed |
| // code is compiled on Unix. |
| // In case such scenario should be supported, the current implementation |
| // will have to be modified in a way to go back to the original Windows |
| // CRITICAL_SECTION layout. That would require to dynamically allocate |
| // the native data and use LockSemaphore as a pointer to it. The current |
| // solution intentionally avoids that since an effort has been made to |
| // make CSs objects completely independent from any other PAL subsystem, |
| // so that they can be used during initialization and shutdown. |
| // In case the "dynamically allocate native data" solution should be |
| // implemented, CSs would acquire a dependency on memory allocation and |
| // thread suspension subsystems, since the first contention on a specific |
| // CS would trigger the native data allocation. |
| // 3) The semantics of the LockCount field has not been kept compatible with |
| // the Windows implementation. |
| // Both on Windows and Unix the lower bit of LockCount indicates |
| // whether or not the CS is locked (for both fair and unfair lock |
| // solution), the second bit indicates whether or not currently there is a |
| // waiter that has been awakened and that is trying to acquire the CS |
| // (only unfair lock solution, unused in the fair one); starting from the |
| // third bit, LockCount represents the number of waiter threads currently |
| // waiting on the CS. |
| // Windows, anyway, implements this semantics in negative logic, so that |
| // an unlocked CS is represented by a LockCount == -1 (i.e. 0xFFFFFFFF, |
| // all the bits set), while on Unix an unlocked CS has LockCount == 0. |
| // Windows needs to use negative logic to support legacy code bad enough |
| // to directly access CS's fields making the assumption that |
| // LockCount == -1 means CS unlocked. Unix will not support that, and |
| // it uses positive logic. |
| // 4) The CRITICAL_SECTION_DEBUG_INFO layout on Unix is intentionally not |
| // compatible with the Windows layout. |
| // 5) For legacy code dependencies issues similar to those just described for |
| // the LockCount field, Windows CS code maintains a per-process list of |
| // debug info for all the CSs, both on debug and free/retail builds. On |
| // Unix such a list is maintained only on debug builds, and no debug |
| // info structure is allocated on free/retail builds |
| // |
| |
| SET_DEFAULT_DEBUG_CHANNEL(CRITSEC); |
| |
| #ifdef TRACE_CS_LOGIC |
| #define CS_TRACE TRACE |
| #else |
| #ifdef __GNUC__ |
| #define CS_TRACE(args...) |
| #else |
| #define CS_TRACE(...) |
| #endif |
| #endif // TRACE_CS_LOGIC |
| |
| // |
| // Note: PALCS_LOCK_WAITER_INC must be 2 * PALCS_LOCK_AWAKENED_WAITER |
| // |
| #define PALCS_LOCK_INIT 0 |
| #define PALCS_LOCK_BIT 1 |
| #define PALCS_LOCK_AWAKENED_WAITER 2 |
| #define PALCS_LOCK_WAITER_INC 4 |
| |
| #define PALCS_GETLBIT(val) ((int)(0!=(PALCS_LOCK_BIT&val))) |
| #define PALCS_GETAWBIT(val) ((int)(0!=(PALCS_LOCK_AWAKENED_WAITER&val))) |
| #define PALCS_GETWCOUNT(val) (val/PALCS_LOCK_WAITER_INC) |
| |
| enum PalCsInitState |
| { |
| PalCsNotInitialized, // Critical section not initialized (InitializedCriticalSection |
| // has not yet been called, or DeleteCriticalsection has been |
| // called). |
| PalCsUserInitialized, // Critical section initialized from the user point of view, |
| // i.e. InitializedCriticalSection has been called. |
| PalCsFullyInitializing, // A thread found the CS locked, this is the first contention on |
| // this CS, and the thread is initializing the CS's native data. |
| PalCsFullyInitialized // Internal CS's native data has been fully initialized. |
| }; |
| |
| enum PalCsWaiterReturnState |
| { |
| PalCsReturnWaiterAwakened, |
| PalCsWaiterDidntWait |
| }; |
| |
| struct _PAL_CRITICAL_SECTION; // fwd declaration |
| |
| typedef struct _CRITICAL_SECTION_DEBUG_INFO |
| { |
| LIST_ENTRY Link; |
| struct _PAL_CRITICAL_SECTION * pOwnerCS; |
| Volatile<ULONG> lAcquireCount; |
| Volatile<ULONG> lEnterCount; |
| Volatile<LONG> lContentionCount; |
| } CRITICAL_SECTION_DEBUG_INFO, *PCRITICAL_SECTION_DEBUG_INFO; |
| |
| typedef struct _PAL_CRITICAL_SECTION_NATIVE_DATA |
| { |
| pthread_mutex_t mutex; |
| pthread_cond_t condition; |
| int iPredicate; |
| } PAL_CRITICAL_SECTION_NATIVE_DATA, *PPAL_CRITICAL_SECTION_NATIVE_DATA; |
| |
| typedef struct _PAL_CRITICAL_SECTION { |
| // Windows part |
| PCRITICAL_SECTION_DEBUG_INFO DebugInfo; |
| Volatile<LONG> LockCount; |
| LONG RecursionCount; |
| SIZE_T OwningThread; |
| HANDLE LockSemaphore; |
| ULONG_PTR SpinCount; |
| // Private Unix part |
| BOOL fInternal; |
| Volatile<PalCsInitState> cisInitState; |
| PAL_CRITICAL_SECTION_NATIVE_DATA csndNativeData; |
| } PAL_CRITICAL_SECTION, *PPAL_CRITICAL_SECTION, *LPPAL_CRITICAL_SECTION; |
| |
| #ifdef _DEBUG |
| namespace CorUnix |
| { |
| PAL_CRITICAL_SECTION g_csPALCSsListLock; |
| LIST_ENTRY g_PALCSList = { &g_PALCSList, &g_PALCSList}; |
| } |
| #endif // _DEBUG |
| |
| #define ObtainCurrentThreadId(thread) ObtainCurrentThreadIdImpl(thread, __func__) |
| static SIZE_T ObtainCurrentThreadIdImpl(CPalThread *pCurrentThread, const char *callingFuncName) |
| { |
| SIZE_T threadId; |
| if(pCurrentThread) |
| { |
| threadId = pCurrentThread->GetThreadId(); |
| _ASSERTE(threadId == THREADSilentGetCurrentThreadId()); |
| } |
| else |
| { |
| threadId = THREADSilentGetCurrentThreadId(); |
| CS_TRACE("Early %s, no pthread data, getting TID internally\n", callingFuncName); |
| } |
| _ASSERTE(0 != threadId); |
| |
| return threadId; |
| } |
| |
| |
| /*++ |
| Function: |
| InitializeCriticalSection |
| |
| See MSDN doc. |
| --*/ |
| void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) |
| { |
| PERF_ENTRY(InitializeCriticalSection); |
| ENTRY("InitializeCriticalSection(lpCriticalSection=%p)\n", |
| lpCriticalSection); |
| |
| InternalInitializeCriticalSectionAndSpinCount(lpCriticalSection, |
| 0, false); |
| |
| LOGEXIT("InitializeCriticalSection returns void\n"); |
| PERF_EXIT(InitializeCriticalSection); |
| } |
| |
| /*++ |
| Function: |
| InitializeCriticalSectionEx - Flags is ignored. |
| |
| See MSDN doc. |
| --*/ |
| BOOL InitializeCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount, DWORD Flags) |
| { |
| PERF_ENTRY(InitializeCriticalSection); |
| ENTRY("InitializeCriticalSectionEx(lpCriticalSection=%p, dwSpinCount=%d, Flags=%d)\n", |
| lpCriticalSection, dwSpinCount, Flags); |
| |
| InternalInitializeCriticalSectionAndSpinCount(lpCriticalSection, dwSpinCount, false); |
| |
| LOGEXIT("InitializeCriticalSectionEx returns TRUE\n"); |
| PERF_EXIT(InitializeCriticalSection); |
| return true; |
| } |
| |
| /*++ |
| Function: |
| InitializeCriticalSectionAndSpinCount |
| |
| See MSDN doc. |
| --*/ |
| BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection, |
| DWORD dwSpinCount) |
| { |
| BOOL bRet = TRUE; |
| PERF_ENTRY(InitializeCriticalSectionAndSpinCount); |
| ENTRY("InitializeCriticalSectionAndSpinCount(lpCriticalSection=%p, " |
| "dwSpinCount=%u)\n", lpCriticalSection, dwSpinCount); |
| |
| InternalInitializeCriticalSectionAndSpinCount(lpCriticalSection, |
| dwSpinCount, false); |
| |
| LOGEXIT("InitializeCriticalSectionAndSpinCount returns BOOL %d\n", |
| bRet); |
| PERF_EXIT(InitializeCriticalSectionAndSpinCount); |
| return bRet; |
| } |
| |
| /*++ |
| Function: |
| DeleteCriticalSection |
| |
| See MSDN doc. |
| --*/ |
| void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection) |
| { |
| PERF_ENTRY(DeleteCriticalSection); |
| ENTRY("DeleteCriticalSection(lpCriticalSection=%p)\n", lpCriticalSection); |
| |
| InternalDeleteCriticalSection(lpCriticalSection); |
| |
| LOGEXIT("DeleteCriticalSection returns void\n"); |
| PERF_EXIT(DeleteCriticalSection); |
| } |
| |
| /*++ |
| Function: |
| EnterCriticalSection |
| |
| See MSDN doc. |
| --*/ |
| void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) |
| { |
| PERF_ENTRY(EnterCriticalSection); |
| ENTRY("EnterCriticalSection(lpCriticalSection=%p)\n", lpCriticalSection); |
| |
| CPalThread * pThread = InternalGetCurrentThread(); |
| |
| InternalEnterCriticalSection(pThread, lpCriticalSection); |
| |
| LOGEXIT("EnterCriticalSection returns void\n"); |
| PERF_EXIT(EnterCriticalSection); |
| } |
| |
| /*++ |
| Function: |
| TryEnterCriticalSection |
| |
| See MSDN doc. |
| --*/ |
| BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) |
| { |
| PERF_ENTRY(TryEnterCriticalSection); |
| ENTRY("TryEnterCriticalSection(lpCriticalSection=%p)\n", lpCriticalSection); |
| |
| CPalThread * pThread = InternalGetCurrentThread(); |
| |
| bool fRet = InternalTryEnterCriticalSection(pThread, |
| lpCriticalSection); |
| |
| LOGEXIT("TryEnterCriticalSection returns bool %d\n", (int)fRet); |
| PERF_EXIT(TryEnterCriticalSection); |
| |
| return (BOOL)fRet; |
| } |
| |
| /*++ |
| Function: |
| LeaveCriticalSection |
| |
| See MSDN doc. |
| --*/ |
| VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection) |
| { |
| PERF_ENTRY(LeaveCriticalSection); |
| ENTRY("LeaveCriticalSection(lpCriticalSection=%p)\n", lpCriticalSection); |
| |
| CPalThread * pThread = InternalGetCurrentThread(); |
| |
| InternalLeaveCriticalSection(pThread, lpCriticalSection); |
| |
| LOGEXIT("LeaveCriticalSection returns void\n"); |
| PERF_EXIT(LeaveCriticalSection); |
| } |
| |
| /*++ |
| Function: |
| InternalInitializeCriticalSection |
| |
| Initializes a critical section. It assumes the CS is an internal one, |
| i.e. thread entering it will be marked unsafe for suspension |
| --*/ |
| VOID InternalInitializeCriticalSection(CRITICAL_SECTION *pcs) |
| { |
| InternalInitializeCriticalSectionAndSpinCount(pcs, 0, true); |
| } |
| |
| /*++ |
| Function: |
| InternalDeleteCriticalSection |
| |
| Deletes a critical section |
| --*/ |
| VOID InternalDeleteCriticalSection( |
| PCRITICAL_SECTION pCriticalSection) |
| { |
| PAL_CRITICAL_SECTION * pPalCriticalSection = |
| reinterpret_cast<PAL_CRITICAL_SECTION*>(pCriticalSection); |
| |
| _ASSERT_MSG(PalCsUserInitialized == pPalCriticalSection->cisInitState || |
| PalCsFullyInitialized == pPalCriticalSection->cisInitState, |
| "CS %p is not initialized", pPalCriticalSection); |
| |
| #ifdef _DEBUG |
| CPalThread * pThread = |
| (PALIsThreadDataInitialized() ? GetCurrentPalThread() : NULL); |
| |
| if (0 != pPalCriticalSection->LockCount) |
| { |
| SIZE_T tid; |
| tid = ObtainCurrentThreadId(pThread); |
| int iWaiterCount = (int)PALCS_GETWCOUNT(pPalCriticalSection->LockCount); |
| |
| if (0 != (PALCS_LOCK_BIT & pPalCriticalSection->LockCount)) |
| { |
| // CS is locked |
| if (tid != pPalCriticalSection->OwningThread) |
| { |
| // not owner |
| ASSERT("Thread tid=%u deleting a CS owned by thread tid=%u\n", |
| tid, pPalCriticalSection->OwningThread); |
| } |
| else |
| { |
| // owner |
| if (0 != iWaiterCount) |
| { |
| ERROR("Thread tid=%u is deleting a CS with %d threads waiting on it\n", |
| tid, iWaiterCount); |
| } |
| else |
| { |
| WARN("Thread tid=%u is deleting a critical section it still owns\n", |
| tid); |
| } |
| } |
| } |
| else |
| { |
| // CS is not locked |
| if (0 != iWaiterCount) |
| { |
| ERROR("Deleting a CS with %d threads waiting on it\n", |
| iWaiterCount); |
| } |
| else |
| { |
| ERROR("Thread tid=%u is deleting a critical section currently not " |
| "owned, but with one waiter awakened\n", tid); |
| } |
| } |
| } |
| |
| if (NULL != pPalCriticalSection->DebugInfo) |
| { |
| if (pPalCriticalSection != &CorUnix::g_csPALCSsListLock) |
| { |
| InternalEnterCriticalSection(pThread, |
| reinterpret_cast<CRITICAL_SECTION*>(&g_csPALCSsListLock)); |
| RemoveEntryList(&pPalCriticalSection->DebugInfo->Link); |
| InternalLeaveCriticalSection(pThread, |
| reinterpret_cast<CRITICAL_SECTION*>(&g_csPALCSsListLock)); |
| } |
| else |
| { |
| RemoveEntryList(&pPalCriticalSection->DebugInfo->Link); |
| } |
| |
| #ifdef PAL_TRACK_CRITICAL_SECTIONS_DATA |
| LONG lVal, lNewVal; |
| Volatile<LONG> * plDest; |
| |
| // Update delete count |
| InterlockedIncrement(pPalCriticalSection->fInternal ? |
| &g_lPALCSInternalDeleteCount : &g_lPALCSDeleteCount); |
| |
| // Update acquire count |
| plDest = pPalCriticalSection->fInternal ? |
| &g_lPALCSInternalAcquireCount : &g_lPALCSAcquireCount; |
| do { |
| lVal = *plDest; |
| lNewVal = lVal + pPalCriticalSection->DebugInfo->lAcquireCount; |
| lNewVal = InterlockedCompareExchange(plDest, lNewVal, lVal); |
| } while (lVal != lNewVal); |
| |
| // Update enter count |
| plDest = pPalCriticalSection->fInternal ? |
| &g_lPALCSInternalEnterCount : &g_lPALCSEnterCount; |
| do { |
| lVal = *plDest; |
| lNewVal = lVal + pPalCriticalSection->DebugInfo->lEnterCount; |
| lNewVal = InterlockedCompareExchange(plDest, lNewVal, lVal); |
| } while (lVal != lNewVal); |
| |
| // Update contention count |
| plDest = pPalCriticalSection->fInternal ? |
| &g_lPALCSInternalContentionCount : &g_lPALCSContentionCount; |
| do { |
| lVal = *plDest; |
| lNewVal = lVal + pPalCriticalSection->DebugInfo->lContentionCount; |
| lNewVal = InterlockedCompareExchange(plDest, lNewVal, lVal); |
| } while (lVal != lNewVal); |
| |
| #endif // PAL_TRACK_CRITICAL_SECTIONS_DATA |
| |
| InternalDelete(pPalCriticalSection->DebugInfo); |
| pPalCriticalSection->DebugInfo = NULL; |
| } |
| #endif // _DEBUG |
| |
| if (PalCsFullyInitialized == pPalCriticalSection->cisInitState) |
| { |
| int iRet; |
| |
| // destroy condition |
| iRet = pthread_cond_destroy(&pPalCriticalSection->csndNativeData.condition); |
| _ASSERT_MSG(0 == iRet, "Failed destroying condition in CS @ %p " |
| "[err=%d]\n", pPalCriticalSection, iRet); |
| |
| // destroy mutex |
| iRet = pthread_mutex_destroy(&pPalCriticalSection->csndNativeData.mutex); |
| _ASSERT_MSG(0 == iRet, "Failed destroying mutex in CS @ %p " |
| "[err=%d]\n", pPalCriticalSection, iRet); |
| } |
| |
| // Reset critical section state |
| pPalCriticalSection->cisInitState = PalCsNotInitialized; |
| } |
| |
| // The following PALCEnterCriticalSection and PALCLeaveCriticalSection |
| // functions are intended to provide CorUnix's InternalEnterCriticalSection |
| // and InternalLeaveCriticalSection functionalities to legacy C code, |
| // which has no knowledge of CPalThread, classes and namespaces. |
| |
| /*++ |
| Function: |
| PALCEnterCriticalSection |
| |
| Provides CorUnix's InternalEnterCriticalSection functionality to legacy C code, |
| which has no knowledge of CPalThread, classes and namespaces. |
| --*/ |
| VOID PALCEnterCriticalSection(CRITICAL_SECTION * pcs) |
| { |
| CPalThread * pThread = |
| (PALIsThreadDataInitialized() ? GetCurrentPalThread() : NULL); |
| CorUnix::InternalEnterCriticalSection(pThread, pcs); |
| } |
| |
| /*++ |
| Function: |
| PALCLeaveCriticalSection |
| |
| Provides CorUnix's InternalLeaveCriticalSection functionality to legacy C code, |
| which has no knowledge of CPalThread, classes and namespaces. |
| --*/ |
| VOID PALCLeaveCriticalSection(CRITICAL_SECTION * pcs) |
| { |
| CPalThread * pThread = |
| (PALIsThreadDataInitialized() ? GetCurrentPalThread() : NULL); |
| CorUnix::InternalLeaveCriticalSection(pThread, pcs); |
| } |
| |
| namespace CorUnix |
| { |
| static PalCsWaiterReturnState PALCS_WaitOnCS( |
| PAL_CRITICAL_SECTION * pPalCriticalSection, |
| LONG lInc); |
| static PAL_ERROR PALCS_DoActualWait(PAL_CRITICAL_SECTION * pPalCriticalSection); |
| static PAL_ERROR PALCS_WakeUpWaiter(PAL_CRITICAL_SECTION * pPalCriticalSection); |
| static bool PALCS_FullyInitialize(PAL_CRITICAL_SECTION * pPalCriticalSection); |
| |
| #ifdef _DEBUG |
| enum CSSubSysInitState |
| { |
| CSSubSysNotInitialzed, |
| CSSubSysInitializing, |
| CSSubSysInitialized |
| }; |
| static Volatile<CSSubSysInitState> csssInitState = CSSubSysNotInitialzed; |
| |
| #ifdef PAL_TRACK_CRITICAL_SECTIONS_DATA |
| static Volatile<LONG> g_lPALCSInitializeCount = 0; |
| static Volatile<LONG> g_lPALCSDeleteCount = 0; |
| static Volatile<LONG> g_lPALCSAcquireCount = 0; |
| static Volatile<LONG> g_lPALCSEnterCount = 0; |
| static Volatile<LONG> g_lPALCSContentionCount = 0; |
| static Volatile<LONG> g_lPALCSInternalInitializeCount = 0; |
| static Volatile<LONG> g_lPALCSInternalDeleteCount = 0; |
| static Volatile<LONG> g_lPALCSInternalAcquireCount = 0; |
| static Volatile<LONG> g_lPALCSInternalEnterCount = 0; |
| static Volatile<LONG> g_lPALCSInternalContentionCount = 0; |
| #endif // PAL_TRACK_CRITICAL_SECTIONS_DATA |
| #endif // _DEBUG |
| |
| |
| /*++ |
| Function: |
| CorUnix::CriticalSectionSubSysInitialize |
| |
| Initializes CS subsystem |
| --*/ |
| void CriticalSectionSubSysInitialize() |
| { |
| static_assert(sizeof(CRITICAL_SECTION) >= sizeof(PAL_CRITICAL_SECTION), |
| "PAL fatal internal error: sizeof(CRITICAL_SECTION) is " |
| "smaller than sizeof(PAL_CRITICAL_SECTION)"); |
| |
| #ifdef _DEBUG |
| LONG lRet = InterlockedCompareExchange((LONG *)&csssInitState, |
| (LONG)CSSubSysInitializing, |
| (LONG)CSSubSysNotInitialzed); |
| if ((LONG)CSSubSysNotInitialzed == lRet) |
| { |
| InitializeListHead(&g_PALCSList); |
| |
| InternalInitializeCriticalSectionAndSpinCount( |
| reinterpret_cast<CRITICAL_SECTION*>(&g_csPALCSsListLock), |
| 0, true); |
| InterlockedExchange((LONG *)&csssInitState, |
| (LONG)CSSubSysInitialized); |
| } |
| else |
| { |
| while (csssInitState != CSSubSysInitialized) |
| { |
| sched_yield(); |
| } |
| } |
| #endif // _DEBUG |
| } |
| |
| /*++ |
| Function: |
| CorUnix::InternalInitializeCriticalSectionAndSpinCount |
| |
| Initializes a CS with the given spin count. If 'fInternal' is true |
| the CS will be treatead as an internal one for its whole lifetime, |
| i.e. any thread that will enter it will be marked as unsafe for |
| suspension as long as it holds the CS |
| --*/ |
| void InternalInitializeCriticalSectionAndSpinCount( |
| PCRITICAL_SECTION pCriticalSection, |
| DWORD dwSpinCount, |
| bool fInternal) |
| { |
| PAL_CRITICAL_SECTION * pPalCriticalSection = |
| reinterpret_cast<PAL_CRITICAL_SECTION*>(pCriticalSection); |
| |
| #ifndef PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| // Make sure bits are defined in a usable way |
| _ASSERTE(PALCS_LOCK_AWAKENED_WAITER * 2 == PALCS_LOCK_WAITER_INC); |
| #endif // !PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| |
| // Make sure structure sizes are compatible |
| _ASSERTE(sizeof(CRITICAL_SECTION) >= sizeof(PAL_CRITICAL_SECTION)); |
| |
| #ifdef _DEBUG |
| if (sizeof(CRITICAL_SECTION) > sizeof(PAL_CRITICAL_SECTION)) |
| { |
| WARN("PAL_CS_NATIVE_DATA_SIZE appears to be defined to a value (%d) " |
| "larger than needed on this platform (%d).\n", |
| sizeof(CRITICAL_SECTION), sizeof(PAL_CRITICAL_SECTION)); |
| } |
| #endif // _DEBUG |
| |
| // Init CS data |
| pPalCriticalSection->DebugInfo = NULL; |
| pPalCriticalSection->LockCount = 0; |
| pPalCriticalSection->RecursionCount = 0; |
| pPalCriticalSection->SpinCount = dwSpinCount; |
| pPalCriticalSection->OwningThread = NULL; |
| pPalCriticalSection->LockSemaphore = NULL; |
| pPalCriticalSection->fInternal = fInternal; |
| |
| #ifdef _DEBUG |
| CPalThread * pThread = |
| (PALIsThreadDataInitialized() ? GetCurrentPalThread() : NULL); |
| |
| pPalCriticalSection->DebugInfo = InternalNew<CRITICAL_SECTION_DEBUG_INFO>(); |
| _ASSERT_MSG(NULL != pPalCriticalSection->DebugInfo, |
| "Failed to allocate debug info for new CS\n"); |
| |
| // Init debug info data |
| pPalCriticalSection->DebugInfo->lAcquireCount = 0; |
| pPalCriticalSection->DebugInfo->lEnterCount = 0; |
| pPalCriticalSection->DebugInfo->lContentionCount = 0; |
| pPalCriticalSection->DebugInfo->pOwnerCS = pPalCriticalSection; |
| |
| // Insert debug info struct in global list |
| if (pPalCriticalSection != &g_csPALCSsListLock) |
| { |
| InternalEnterCriticalSection(pThread, |
| reinterpret_cast<CRITICAL_SECTION*>(&g_csPALCSsListLock)); |
| InsertTailList(&g_PALCSList, &pPalCriticalSection->DebugInfo->Link); |
| InternalLeaveCriticalSection(pThread, |
| reinterpret_cast<CRITICAL_SECTION*>(&g_csPALCSsListLock)); |
| } |
| else |
| { |
| InsertTailList(&g_PALCSList, &pPalCriticalSection->DebugInfo->Link); |
| } |
| |
| #ifdef PAL_TRACK_CRITICAL_SECTIONS_DATA |
| InterlockedIncrement(fInternal ? |
| &g_lPALCSInternalInitializeCount : &g_lPALCSInitializeCount); |
| #endif // PAL_TRACK_CRITICAL_SECTIONS_DATA |
| #endif // _DEBUG |
| |
| // Set initializazion state |
| pPalCriticalSection->cisInitState = PalCsUserInitialized; |
| |
| #ifdef MUTEX_BASED_CSS |
| bool fInit; |
| do |
| { |
| fInit = PALCS_FullyInitialize(pPalCriticalSection); |
| _ASSERTE(fInit); |
| } while (!fInit && 0 == sched_yield()); |
| |
| if (fInit) |
| { |
| // Set initializazion state |
| pPalCriticalSection->cisInitState = PalCsFullyInitialized; |
| } |
| #endif // MUTEX_BASED_CSS |
| } |
| |
| #ifndef MUTEX_BASED_CSS |
| /*++ |
| Function: |
| CorUnix::InternalEnterCriticalSection |
| |
| Enters a CS, causing the thread to block if the CS is owned by |
| another thread |
| --*/ |
| void InternalEnterCriticalSection( |
| CPalThread * pThread, |
| PCRITICAL_SECTION pCriticalSection) |
| { |
| PAL_CRITICAL_SECTION * pPalCriticalSection = |
| reinterpret_cast<PAL_CRITICAL_SECTION*>(pCriticalSection); |
| |
| LONG lSpinCount; |
| LONG lVal, lNewVal; |
| LONG lBitsToChange, lWaitInc; |
| PalCsWaiterReturnState cwrs; |
| SIZE_T threadId; |
| |
| _ASSERTE(PalCsNotInitialized != pPalCriticalSection->cisInitState); |
| |
| threadId = ObtainCurrentThreadId(pThread); |
| |
| |
| // Check if the current thread already owns the CS |
| // |
| // Note: there is no need for this double check to be atomic. In fact |
| // if the first check fails, the second doesn't count (and it's not |
| // even executed). If the first one succeeds and the second one |
| // doesn't, it doesn't matter if LockCount has already changed by the |
| // time OwningThread is tested. Instead, if the first one succeeded, |
| // and the second also succeeds, LockCount cannot have changed in the |
| // meanwhile, since this is the owning thread and only the owning |
| // thread can change the lock bit when the CS is owned. |
| if ((pPalCriticalSection->LockCount & PALCS_LOCK_BIT) && |
| (pPalCriticalSection->OwningThread == threadId)) |
| { |
| pPalCriticalSection->RecursionCount += 1; |
| #ifdef _DEBUG |
| if (NULL != pPalCriticalSection->DebugInfo) |
| { |
| pPalCriticalSection->DebugInfo->lEnterCount += 1; |
| } |
| #endif // _DEBUG |
| goto IECS_exit; |
| } |
| |
| // Set bits to change and waiter increment for an incoming thread |
| lBitsToChange = PALCS_LOCK_BIT; |
| lWaitInc = PALCS_LOCK_WAITER_INC; |
| lSpinCount = pPalCriticalSection->SpinCount; |
| |
| while (TRUE) |
| { |
| // Either this is an incoming thread, and therefore lBitsToChange |
| // is just PALCS_LOCK_BIT, or this is an awakened waiter |
| _ASSERTE(PALCS_LOCK_BIT == lBitsToChange || |
| (PALCS_LOCK_BIT | PALCS_LOCK_AWAKENED_WAITER) == lBitsToChange); |
| |
| // Make sure the waiter increment is in a valid range |
| _ASSERTE(PALCS_LOCK_WAITER_INC == lWaitInc || |
| PALCS_LOCK_AWAKENED_WAITER == lWaitInc); |
| |
| do { |
| lVal = pPalCriticalSection->LockCount; |
| |
| while (0 == (lVal & PALCS_LOCK_BIT)) |
| { |
| // CS is not locked: try lo lock it |
| |
| // Make sure that whether we are an incoming thread |
| // or the PALCS_LOCK_AWAKENED_WAITER bit is set |
| _ASSERTE((PALCS_LOCK_BIT == lBitsToChange) || |
| (PALCS_LOCK_AWAKENED_WAITER & lVal)); |
| |
| lNewVal = lVal ^ lBitsToChange; |
| |
| // Make sure we are actually trying to lock |
| _ASSERTE(lNewVal & PALCS_LOCK_BIT); |
| |
| CS_TRACE("[ECS %p] Switching from {%d, %d, %d} to " |
| "{%d, %d, %d} ==>\n", pPalCriticalSection, |
| PALCS_GETWCOUNT(lVal), PALCS_GETAWBIT(lVal), PALCS_GETLBIT(lVal), |
| PALCS_GETWCOUNT(lNewVal), PALCS_GETAWBIT(lNewVal), PALCS_GETLBIT(lNewVal)); |
| |
| // Try to switch the value |
| lNewVal = InterlockedCompareExchange (&pPalCriticalSection->LockCount, |
| lNewVal, lVal); |
| |
| CS_TRACE("[ECS %p] ==> %s LockCount={%d, %d, %d} " |
| "lVal={%d, %d, %d}\n", pPalCriticalSection, |
| (lNewVal == lVal) ? "OK" : "NO", |
| PALCS_GETWCOUNT(pPalCriticalSection->LockCount), |
| PALCS_GETAWBIT(pPalCriticalSection->LockCount), |
| PALCS_GETLBIT(pPalCriticalSection->LockCount), |
| PALCS_GETWCOUNT(lVal), PALCS_GETAWBIT(lVal), PALCS_GETLBIT(lVal)); |
| |
| if (lNewVal == lVal) |
| { |
| // CS successfully acquired |
| goto IECS_set_ownership; |
| } |
| |
| // Acquisition failed, some thread raced with us; |
| // update value for next loop |
| lVal = lNewVal; |
| } |
| |
| if (0 < lSpinCount) |
| { |
| sched_yield(); |
| } |
| } while (0 <= --lSpinCount); |
| |
| cwrs = PALCS_WaitOnCS(pPalCriticalSection, lWaitInc); |
| |
| if (PalCsReturnWaiterAwakened == cwrs) |
| { |
| #ifdef PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| // |
| // Fair Critical Sections |
| // |
| // In the fair lock case, when a waiter wakes up the CS |
| // must be locked (i.e. ownership passed on to the waiter) |
| _ASSERTE(0 != (PALCS_LOCK_BIT & pPalCriticalSection->LockCount)); |
| |
| // CS successfully acquired |
| goto IECS_set_ownership; |
| |
| #else // PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| // |
| // Unfair Critical Sections |
| // |
| _ASSERTE(PALCS_LOCK_AWAKENED_WAITER & pPalCriticalSection->LockCount); |
| |
| lBitsToChange = PALCS_LOCK_BIT | PALCS_LOCK_AWAKENED_WAITER; |
| lWaitInc = PALCS_LOCK_AWAKENED_WAITER; |
| #endif // PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| } |
| } |
| |
| IECS_set_ownership: |
| // Critical section acquired: set ownership data |
| pPalCriticalSection->OwningThread = threadId; |
| pPalCriticalSection->RecursionCount = 1; |
| #ifdef _DEBUG |
| if (NULL != pPalCriticalSection->DebugInfo) |
| { |
| pPalCriticalSection->DebugInfo->lAcquireCount += 1; |
| pPalCriticalSection->DebugInfo->lEnterCount += 1; |
| } |
| #endif // _DEBUG |
| |
| IECS_exit: |
| return; |
| } |
| |
| /*++ |
| Function: |
| CorUnix::InternalLeaveCriticalSection |
| |
| Leaves a currently owned CS |
| --*/ |
| void InternalLeaveCriticalSection(CPalThread * pThread, |
| PCRITICAL_SECTION pCriticalSection) |
| { |
| PAL_CRITICAL_SECTION * pPalCriticalSection = |
| reinterpret_cast<PAL_CRITICAL_SECTION*>(pCriticalSection); |
| LONG lVal, lNewVal; |
| |
| #ifdef _DEBUG |
| SIZE_T threadId; |
| |
| _ASSERTE(PalCsNotInitialized != pPalCriticalSection->cisInitState); |
| |
| threadId = ObtainCurrentThreadId(pThread); |
| _ASSERTE(threadId == pPalCriticalSection->OwningThread); |
| #endif // _DEBUG |
| |
| _ASSERT_MSG(PALCS_LOCK_BIT & pPalCriticalSection->LockCount, |
| "Trying to release an unlocked CS\n"); |
| _ASSERT_MSG(0 < pPalCriticalSection->RecursionCount, |
| "Trying to release an unlocked CS\n"); |
| |
| if (--pPalCriticalSection->RecursionCount > 0) |
| { |
| // Recursion was > 1, still owning the CS |
| goto ILCS_cs_exit; |
| } |
| |
| // Reset CS ownership |
| pPalCriticalSection->OwningThread = NULL; |
| |
| // Load the current LockCount value |
| lVal = pPalCriticalSection->LockCount; |
| |
| while (true) |
| { |
| _ASSERT_MSG(0 != (PALCS_LOCK_BIT & lVal), |
| "Trying to release an unlocked CS\n"); |
| |
| // NB: In the fair lock case (PALCS_TRANSFER_OWNERSHIP_ON_RELEASE) the |
| // PALCS_LOCK_AWAKENED_WAITER bit is not used |
| if ( (PALCS_LOCK_BIT == lVal) |
| #ifndef PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| || (PALCS_LOCK_AWAKENED_WAITER & lVal) |
| #endif // !PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| ) |
| { |
| // Whether there are no waiters (PALCS_LOCK_BIT == lVal) |
| // or a waiter has already been awakened, therefore we |
| // just need to reset the lock bit and return |
| lNewVal = lVal & ~PALCS_LOCK_BIT; |
| CS_TRACE("[LCS-UN %p] Switching from {%d, %d, %d} to " |
| "{%d, %d, %d} ==>\n", pPalCriticalSection, |
| PALCS_GETWCOUNT(lVal), PALCS_GETAWBIT(lVal), PALCS_GETLBIT(lVal), |
| PALCS_GETWCOUNT(lNewVal), PALCS_GETAWBIT(lNewVal), PALCS_GETLBIT(lNewVal)); |
| |
| lNewVal = InterlockedCompareExchange(&pPalCriticalSection->LockCount, |
| lNewVal, lVal); |
| |
| CS_TRACE("[LCS-UN %p] ==> %s\n", pPalCriticalSection, |
| (lNewVal == lVal) ? "OK" : "NO"); |
| |
| if (lNewVal == lVal) |
| { |
| goto ILCS_cs_exit; |
| } |
| } |
| else |
| { |
| // There is at least one waiter, we need to wake it up |
| |
| #ifdef PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| // Fair lock case: passing ownership on to the first waiter. |
| // Here we need only to decrement the waiters count. CS will |
| // remain locked and ownership will be passed to the waiter, |
| // which will take care of setting ownership data as soon as |
| // it wakes up |
| lNewVal = lVal - PALCS_LOCK_WAITER_INC; |
| #else // PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| // Unfair lock case: we need to atomically decrement the waiters |
| // count (we are about ot wake up one of them), set the |
| // "waiter awakened" bit and to reset the "CS locked" bit. |
| // Note that, since we know that at this time PALCS_LOCK_BIT |
| // is set and PALCS_LOCK_AWAKENED_WAITER is not set, none of |
| // the addenda will affect bits other than its target bit(s), |
| // i.e. PALCS_LOCK_BIT will not affect PALCS_LOCK_AWAKENED_WAITER, |
| // PALCS_LOCK_AWAKENED_WAITER will not affect the actual |
| // count of waiters, and the latter will not change the two |
| // former ones |
| lNewVal = lVal - PALCS_LOCK_WAITER_INC + |
| PALCS_LOCK_AWAKENED_WAITER - PALCS_LOCK_BIT; |
| #endif // PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| CS_TRACE("[LCS-CN %p] Switching from {%d, %d, %d} to {%d, %d, %d} ==>\n", |
| pPalCriticalSection, |
| PALCS_GETWCOUNT(lVal), PALCS_GETAWBIT(lVal), PALCS_GETLBIT(lVal), |
| PALCS_GETWCOUNT(lNewVal), PALCS_GETAWBIT(lNewVal), PALCS_GETLBIT(lNewVal)); |
| |
| lNewVal = InterlockedCompareExchange(&pPalCriticalSection->LockCount, |
| lNewVal, lVal); |
| |
| CS_TRACE("[LCS-CN %p] ==> %s\n", pPalCriticalSection, |
| (lNewVal == lVal) ? "OK" : "NO"); |
| |
| if (lNewVal == lVal) |
| { |
| // Wake up the waiter |
| PALCS_WakeUpWaiter (pPalCriticalSection); |
| |
| #ifdef PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| // In the fair lock case, we need to yield here to defeat |
| // the inherently unfair nature of the condition/predicate |
| // construct |
| sched_yield(); |
| #endif // PALCS_TRANSFER_OWNERSHIP_ON_RELEASE |
| |
| goto ILCS_cs_exit; |
| } |
| } |
| |
| // CS unlock failed due to race with another thread trying to |
| // register as waiter on it. We need to keep on looping. We |
| // intentionally do not yield here in order to reserve higher |
| // priority for the releasing thread. |
| // |
| // At this point lNewVal contains the latest LockCount value |
| // retrieved by one of the two InterlockedCompareExchange above; |
| // we can use this value as expected LockCount for the next loop, |
| // without the need to fetch it again. |
| lVal = lNewVal; |
| } |
| |
| ILCS_cs_exit: |
| return; |
| } |
| |
| /*++ |
| Function: |
| CorUnix::InternalTryEnterCriticalSection |
| |
| Tries to acquire a CS. It returns true on success, false if the CS is |
| locked by another thread |
| --*/ |
| bool InternalTryEnterCriticalSection( |
| CPalThread * pThread, |
| PCRITICAL_SECTION pCriticalSection) |
| { |
| PAL_CRITICAL_SECTION * pPalCriticalSection = |
| reinterpret_cast<PAL_CRITICAL_SECTION*>(pCriticalSection); |
| |
| LONG lNewVal; |
| SIZE_T threadId; |
| bool fRet = true; |
| |
| _ASSERTE(PalCsNotInitialized != pPalCriticalSection->cisInitState); |
| |
| threadId = ObtainCurrentThreadId(pThread); |
| |
| lNewVal = InterlockedCompareExchange (&pPalCriticalSection->LockCount, |
| (LONG)PALCS_LOCK_BIT, |
| (LONG)PALCS_LOCK_INIT); |
| if (lNewVal == PALCS_LOCK_INIT) |
| { |
| // CS successfully acquired: setting ownership data |
| pPalCriticalSection->OwningThread = threadId; |
| pPalCriticalSection->RecursionCount = 1; |
| #ifdef _DEBUG |
| if (NULL != pPalCriticalSection->DebugInfo) |
| { |
| pPalCriticalSection->DebugInfo->lAcquireCount += 1; |
| pPalCriticalSection->DebugInfo->lEnterCount += 1; |
| } |
| #endif // _DEBUG |
| |
| goto ITECS_exit; |
| } |
| |
| // check if the current thread already owns the criticalSection |
| if ((lNewVal & PALCS_LOCK_BIT) && |
| (pPalCriticalSection->OwningThread == threadId)) |
| { |
| pPalCriticalSection->RecursionCount += 1; |
| #ifdef _DEBUG |
| if (NULL != pPalCriticalSection->DebugInfo) |
| { |
| pPalCriticalSection->DebugInfo->lEnterCount += 1; |
| } |
| #endif // _DEBUG |
| |
| goto ITECS_exit; |
| } |
| |
| // Failed to acquire the CS |
| fRet = false; |
| |
| ITECS_exit: |
| return fRet; |
| } |
| #endif // MUTEX_BASED_CSS |
| |
| /*++ |
| Function: |
| CorUnix::PALCS_FullyInitialize |
| |
| Fully initializes a CS previously initialied true InitializeCriticalSection. |
| This method is called at the first contention on the target CS |
| --*/ |
| bool PALCS_FullyInitialize(PAL_CRITICAL_SECTION * pPalCriticalSection) |
| { |
| LONG lVal, lNewVal; |
| bool fRet = true; |
| |
| lVal = pPalCriticalSection->cisInitState; |
| if (PalCsFullyInitialized == lVal) |
| { |
| goto PCDI_exit; |
| } |
| if (PalCsUserInitialized == lVal) |
| { |
| int iRet; |
| lNewVal = (LONG)PalCsFullyInitializing; |
| lNewVal = InterlockedCompareExchange( |
| (LONG *)&pPalCriticalSection->cisInitState, lNewVal, lVal); |
| if (lNewVal != lVal) |
| { |
| if (PalCsFullyInitialized == lNewVal) |
| { |
| // Another thread did initialize this CS: we can |
| // safely return 'true' |
| goto PCDI_exit; |
| } |
| |
| // Another thread is still initializing this CS: yield and |
| // spin by returning 'false' |
| sched_yield(); |
| fRet = false; |
| goto PCDI_exit; |
| } |
| |
| // |
| // Actual native initialization |
| // |
| // Mutex |
| iRet = pthread_mutex_init(&pPalCriticalSection->csndNativeData.mutex, NULL); |
| if (0 != iRet) |
| { |
| ASSERT("Failed initializing mutex in CS @ %p [err=%d]\n", |
| pPalCriticalSection, iRet); |
| pPalCriticalSection->cisInitState = PalCsUserInitialized; |
| fRet = false; |
| goto PCDI_exit; |
| } |
| #ifndef MUTEX_BASED_CSS |
| // Condition |
| iRet = pthread_cond_init(&pPalCriticalSection->csndNativeData.condition, NULL); |
| if (0 != iRet) |
| { |
| ASSERT("Failed initializing condition in CS @ %p [err=%d]\n", |
| pPalCriticalSection, iRet); |
| pthread_mutex_destroy(&pPalCriticalSection->csndNativeData.mutex); |
| pPalCriticalSection->cisInitState = PalCsUserInitialized; |
| fRet = false; |
| goto PCDI_exit; |
| } |
| // Predicate |
| pPalCriticalSection->csndNativeData.iPredicate = 0; |
| #endif |
| |
| pPalCriticalSection->cisInitState = PalCsFullyInitialized; |
| } |
| else if (PalCsFullyInitializing == lVal) |
| { |
| // Another thread is still initializing this CS: yield and |
| // spin by returning 'false' |
| sched_yield(); |
| fRet = false; |
| goto PCDI_exit; |
| } |
| else |
| { |
| ASSERT("CS %p is not initialized", pPalCriticalSection); |
| fRet = false; |
| goto PCDI_exit; |
| } |
| |
| PCDI_exit: |
| return fRet; |
| } |
| |
| |
| /*++ |
| Function: |
| CorUnix::PALCS_WaitOnCS |
| |
| Waits on a CS owned by anothr thread. It returns PalCsReturnWaiterAwakened |
| if the thread actually waited on the CS and it has been awakened on CS |
| release. It returns PalCsWaiterDidntWait if another thread is currently |
| fully-initializing the CS and therefore the current thread couldn't wait |
| on it |
| --*/ |
| PalCsWaiterReturnState PALCS_WaitOnCS(PAL_CRITICAL_SECTION * pPalCriticalSection, |
| LONG lInc) |
| { |
| DWORD lVal, lNewVal; |
| PAL_ERROR palErr = NO_ERROR; |
| |
| if (PalCsFullyInitialized != pPalCriticalSection->cisInitState) |
| { |
| // First contention, the CS native wait support need to be |
| // initialized at this time |
| if (!PALCS_FullyInitialize(pPalCriticalSection)) |
| { |
| // The current thread failed the full initialization of the CS, |
| // whether because another thread is race-initializing it, or |
| // there are no enough memory/resources at this time, or |
| // InitializeCriticalSection has never been called. By |
| // returning we will cause the thread to spin on CS trying |
| // again until the CS is initialized |
| return PalCsWaiterDidntWait; |
| } |
| } |
| |
| // Make sure we have a valid waiter increment |
| _ASSERTE(PALCS_LOCK_WAITER_INC == lInc || |
| PALCS_LOCK_AWAKENED_WAITER == lInc); |
| |
| do { |
| lVal = pPalCriticalSection->LockCount; |
| |
| // Make sure the waiter increment is compatible with the |
| // awakened waiter bit value |
| _ASSERTE(PALCS_LOCK_WAITER_INC == lInc || |
| PALCS_LOCK_AWAKENED_WAITER & lVal); |
| |
| if (0 == (lVal & PALCS_LOCK_BIT)) |
| { |
| // the CS is no longer locked, let's bail out |
| return PalCsWaiterDidntWait; |
| } |
| |
| lNewVal = lVal + lInc; |
| |
| // Make sure that this thread was whether an incoming one or it |
| // was an awakened waiter and, in this case, we are now going to |
| // turn off the awakened waiter bit |
| _ASSERT_MSG(PALCS_LOCK_WAITER_INC == lInc || |
| 0 == (PALCS_LOCK_AWAKENED_WAITER & lNewVal)); |
| |
| CS_TRACE("[WCS %p] Switching from {%d, %d, %d} to " |
| "{%d, %d, %d} ==> ", pPalCriticalSection, |
| PALCS_GETWCOUNT(lVal), PALCS_GETAWBIT(lVal), PALCS_GETLBIT(lVal), |
| PALCS_GETWCOUNT(lNewVal), PALCS_GETAWBIT(lNewVal), PALCS_GETLBIT(lNewVal)); |
| |
| lNewVal = InterlockedCompareExchange (&pPalCriticalSection->LockCount, |
| lNewVal, lVal); |
| |
| CS_TRACE("[WCS %p] ==> %s\n", pPalCriticalSection, |
| (lNewVal == lVal) ? "OK" : "NO"); |
| |
| } while (lNewVal != lVal); |
| |
| #ifdef _DEBUG |
| if (NULL != pPalCriticalSection->DebugInfo) |
| { |
| pPalCriticalSection->DebugInfo->lContentionCount += 1; |
| } |
| #endif // _DEBUG |
| |
| // Do the actual native wait |
| palErr = PALCS_DoActualWait(pPalCriticalSection); |
| _ASSERT_MSG(NO_ERROR == palErr, "Native CS wait failed\n"); |
| |
| return PalCsReturnWaiterAwakened; |
| } |
| |
| /*++ |
| Function: |
| CorUnix::PALCS_DoActualWait |
| |
| Performs the actual native wait on the CS |
| --*/ |
| PAL_ERROR PALCS_DoActualWait(PAL_CRITICAL_SECTION * pPalCriticalSection) |
| { |
| int iRet; |
| PAL_ERROR palErr = NO_ERROR; |
| |
| CS_TRACE("Trying to go to sleep [CS=%p]\n", pPalCriticalSection); |
| |
| // Lock the mutex |
| iRet = pthread_mutex_lock(&pPalCriticalSection->csndNativeData.mutex); |
| if (0 != iRet) |
| { |
| palErr = ERROR_INTERNAL_ERROR; |
| goto PCDAW_exit; |
| } |
| |
| CS_TRACE("Actually Going to sleep [CS=%p]\n", pPalCriticalSection); |
| |
| while (0 == pPalCriticalSection->csndNativeData.iPredicate) |
| { |
| // Wait on the condition |
| iRet = pthread_cond_wait(&pPalCriticalSection->csndNativeData.condition, |
| &pPalCriticalSection->csndNativeData.mutex); |
| |
| CS_TRACE("Got a signal on condition [pred=%d]!\n", |
| pPalCriticalSection->csndNativeData.iPredicate); |
| if (0 != iRet) |
| { |
| // Failed: unlock the mutex and bail out |
| ASSERT("Failed waiting on condition in CS %p [err=%d]\n", |
| pPalCriticalSection, iRet); |
| pthread_mutex_unlock(&pPalCriticalSection->csndNativeData.mutex); |
| palErr = ERROR_INTERNAL_ERROR; |
| goto PCDAW_exit; |
| } |
| } |
| |
| // Reset the predicate |
| pPalCriticalSection->csndNativeData.iPredicate = 0; |
| |
| // Unlock the mutex |
| iRet = pthread_mutex_unlock(&pPalCriticalSection->csndNativeData.mutex); |
| if (0 != iRet) |
| { |
| palErr = ERROR_INTERNAL_ERROR; |
| goto PCDAW_exit; |
| } |
| |
| PCDAW_exit: |
| |
| CS_TRACE("Just woken up [CS=%p]\n", pPalCriticalSection); |
| |
| return palErr; |
| } |
| |
| /*++ |
| Function: |
| CorUnix::PALCS_WakeUpWaiter |
| |
| Wakes up the first thread waiting on the CS |
| --*/ |
| PAL_ERROR PALCS_WakeUpWaiter(PAL_CRITICAL_SECTION * pPalCriticalSection) |
| { |
| int iRet; |
| PAL_ERROR palErr = NO_ERROR; |
| |
| _ASSERT_MSG(PalCsFullyInitialized == pPalCriticalSection->cisInitState, |
| "Trying to wake up a waiter on CS not fully initialized\n"); |
| |
| // Lock the mutex |
| iRet = pthread_mutex_lock(&pPalCriticalSection->csndNativeData.mutex); |
| if (0 != iRet) |
| { |
| palErr = ERROR_INTERNAL_ERROR; |
| goto PCWUW_exit; |
| } |
| |
| // Set the predicate |
| pPalCriticalSection->csndNativeData.iPredicate = 1; |
| |
| CS_TRACE("Signaling condition/predicate [pred=%d]!\n", |
| pPalCriticalSection->csndNativeData.iPredicate); |
| |
| // Signal the condition |
| iRet = pthread_cond_signal(&pPalCriticalSection->csndNativeData.condition); |
| if (0 != iRet) |
| { |
| // Failed: set palErr, but continue in order to unlock |
| // the mutex anyway |
| ASSERT("Failed setting condition in CS %p [ret=%d]\n", |
| pPalCriticalSection, iRet); |
| palErr = ERROR_INTERNAL_ERROR; |
| } |
| |
| // Unlock the mutex |
| iRet = pthread_mutex_unlock(&pPalCriticalSection->csndNativeData.mutex); |
| if (0 != iRet) |
| { |
| palErr = ERROR_INTERNAL_ERROR; |
| goto PCWUW_exit; |
| } |
| |
| PCWUW_exit: |
| return palErr; |
| } |
| |
| #ifdef _DEBUG |
| /*++ |
| Function: |
| CorUnix::PALCS_ReportStatisticalData |
| |
| Report creation/acquisition/contention statistical data for the all the |
| CSs so far existed and no longer existing in the current process |
| --*/ |
| void PALCS_ReportStatisticalData() |
| { |
| #ifdef PAL_TRACK_CRITICAL_SECTIONS_DATA |
| CPalThread * pThread = InternalGetCurrentThread(); |
| |
| if (NULL == pThread) DebugBreak(); |
| |
| // Take the lock for the global list of CS debug infos |
| InternalEnterCriticalSection(pThread, (CRITICAL_SECTION*)&g_csPALCSsListLock); |
| |
| LONG lPALCSInitializeCount = g_lPALCSInitializeCount; |
| LONG lPALCSDeleteCount = g_lPALCSDeleteCount; |
| LONG lPALCSAcquireCount = g_lPALCSAcquireCount; |
| LONG lPALCSEnterCount = g_lPALCSEnterCount; |
| LONG lPALCSContentionCount = g_lPALCSContentionCount; |
| LONG lPALCSInternalInitializeCount = g_lPALCSInternalInitializeCount; |
| LONG lPALCSInternalDeleteCount = g_lPALCSInternalDeleteCount; |
| LONG lPALCSInternalAcquireCount = g_lPALCSInternalAcquireCount; |
| LONG lPALCSInternalEnterCount = g_lPALCSInternalEnterCount; |
| LONG lPALCSInternalContentionCount = g_lPALCSInternalContentionCount; |
| |
| PLIST_ENTRY pItem = g_PALCSList.Flink; |
| while (&g_PALCSList != pItem) |
| { |
| PCRITICAL_SECTION_DEBUG_INFO pDebugInfo = |
| (PCRITICAL_SECTION_DEBUG_INFO)pItem; |
| |
| if (pDebugInfo->pOwnerCS->fInternal) |
| { |
| lPALCSInternalAcquireCount += pDebugInfo->lAcquireCount; |
| lPALCSInternalEnterCount += pDebugInfo->lEnterCount; |
| lPALCSInternalContentionCount += pDebugInfo->lContentionCount; |
| } |
| else |
| { |
| lPALCSAcquireCount += pDebugInfo->lAcquireCount; |
| lPALCSEnterCount += pDebugInfo->lEnterCount; |
| lPALCSContentionCount += pDebugInfo->lContentionCount; |
| } |
| |
| pItem = pItem->Flink; |
| } |
| |
| // Release the lock for the global list of CS debug infos |
| InternalLeaveCriticalSection(pThread, (CRITICAL_SECTION*)&g_csPALCSsListLock); |
| |
| TRACE("Critical Sections Statistical Data:\n"); |
| TRACE("{\n"); |
| TRACE(" Client code CSs:\n"); |
| TRACE(" {\n"); |
| TRACE(" Initialize Count: %d\n", lPALCSInitializeCount); |
| TRACE(" Delete Count: %d\n", lPALCSDeleteCount); |
| TRACE(" Acquire Count: %d\n", lPALCSAcquireCount); |
| TRACE(" Enter Count: %d\n", lPALCSEnterCount); |
| TRACE(" Contention Count: %d\n", lPALCSContentionCount); |
| TRACE(" }\n"); |
| TRACE(" Internal PAL CSs:\n"); |
| TRACE(" {\n"); |
| TRACE(" Initialize Count: %d\n", lPALCSInternalInitializeCount); |
| TRACE(" Delete Count: %d\n", lPALCSInternalDeleteCount); |
| TRACE(" Acquire Count: %d\n", lPALCSInternalAcquireCount); |
| TRACE(" Enter Count: %d\n", lPALCSInternalEnterCount); |
| TRACE(" Contention Count: %d\n", lPALCSInternalContentionCount); |
| TRACE(" }\n"); |
| TRACE("}\n"); |
| #endif // PAL_TRACK_CRITICAL_SECTIONS_DATA |
| } |
| |
| /*++ |
| Function: |
| CorUnix::PALCS_DumpCSList |
| |
| Dumps the list of all the CS currently existing in this process. |
| --*/ |
| void PALCS_DumpCSList() |
| { |
| CPalThread * pThread = InternalGetCurrentThread(); |
| |
| // Take the lock for the global list of CS debug infos |
| InternalEnterCriticalSection(pThread, (CRITICAL_SECTION*)&g_csPALCSsListLock); |
| |
| PLIST_ENTRY pItem = g_PALCSList.Flink; |
| while (&g_PALCSList != pItem) |
| { |
| PCRITICAL_SECTION_DEBUG_INFO pDebugInfo = |
| (PCRITICAL_SECTION_DEBUG_INFO)pItem; |
| PPAL_CRITICAL_SECTION pCS = pDebugInfo->pOwnerCS; |
| |
| printf("CS @ %p \n" |
| "{\tDebugInfo = %p -> \n", |
| pCS, pDebugInfo); |
| |
| printf("\t{\n\t\t[Link]\n\t\tpOwnerCS = %p\n" |
| "\t\tAcquireCount \t= %d\n" |
| "\t\tEnterCount \t= %d\n" |
| "\t\tContentionCount = %d\n", |
| pDebugInfo->pOwnerCS, pDebugInfo->lAcquireCount.Load(), |
| pDebugInfo->lEnterCount.Load(), pDebugInfo->lContentionCount.Load()); |
| printf("\t}\n"); |
| |
| printf("\tLockCount \t= %#x\n" |
| "\tRecursionCount \t= %d\n" |
| "\tOwningThread \t= %p\n" |
| "\tLockSemaphore \t= %p\n" |
| "\tSpinCount \t= %u\n" |
| "\tfInternal \t= %d\n" |
| "\teInitState \t= %u\n" |
| "\tpNativeData \t= %p ->\n", |
| pCS->LockCount.Load(), pCS->RecursionCount, (void *)pCS->OwningThread, |
| pCS->LockSemaphore, (unsigned)pCS->SpinCount, (int)pCS->fInternal, |
| pCS->cisInitState.Load(), &pCS->csndNativeData); |
| |
| printf("\t{\n\t\t[mutex]\n\t\t[condition]\n" |
| "\t\tPredicate \t= %d\n" |
| "\t}\n}\n",pCS->csndNativeData.iPredicate); |
| |
| printf("}\n"); |
| |
| pItem = pItem->Flink; |
| } |
| |
| // Release the lock for the global list of CS debug infos |
| InternalLeaveCriticalSection(pThread, (CRITICAL_SECTION*)&g_csPALCSsListLock); |
| } |
| #endif // _DEBUG |
| |
| |
| #if defined(MUTEX_BASED_CSS) || defined(_DEBUG) |
| /*++ |
| Function: |
| CorUnix::InternalEnterCriticalSection |
| |
| Enters a CS, causing the thread to block if the CS is owned by |
| another thread |
| --*/ |
| #ifdef MUTEX_BASED_CSS |
| void InternalEnterCriticalSection( |
| CPalThread * pThread, |
| PCRITICAL_SECTION pCriticalSection) |
| #else // MUTEX_BASED_CSS |
| void MTX_InternalEnterCriticalSection( |
| CPalThread * pThread, |
| PCRITICAL_SECTION pCriticalSection) |
| #endif // MUTEX_BASED_CSS |
| |
| { |
| PAL_CRITICAL_SECTION * pPalCriticalSection = |
| reinterpret_cast<PAL_CRITICAL_SECTION*>(pCriticalSection); |
| int iRet; |
| SIZE_T threadId; |
| |
| _ASSERTE(PalCsNotInitialized != pPalCriticalSection->cisInitState); |
| |
| threadId = ObtainCurrentThreadId(pThread); |
| |
| /* check if the current thread already owns the criticalSection */ |
| if (pPalCriticalSection->OwningThread == threadId) |
| { |
| _ASSERTE(0 < pPalCriticalSection->RecursionCount); |
| pPalCriticalSection->RecursionCount += 1; |
| return; |
| } |
| |
| iRet = pthread_mutex_lock(&pPalCriticalSection->csndNativeData.mutex); |
| _ASSERTE(0 == iRet); |
| |
| pPalCriticalSection->OwningThread = threadId; |
| pPalCriticalSection->RecursionCount = 1; |
| } |
| |
| |
| /*++ |
| Function: |
| CorUnix::InternalLeaveCriticalSection |
| |
| Leaves a currently owned CS |
| --*/ |
| #ifdef MUTEX_BASED_CSS |
| void InternalLeaveCriticalSection( |
| CPalThread * pThread, |
| PCRITICAL_SECTION pCriticalSection) |
| #else // MUTEX_BASED_CSS |
| void MTX_InternalLeaveCriticalSection( |
| CPalThread * pThread, |
| PCRITICAL_SECTION pCriticalSection) |
| #endif // MUTEX_BASED_CSS |
| { |
| PAL_CRITICAL_SECTION * pPalCriticalSection = |
| reinterpret_cast<PAL_CRITICAL_SECTION*>(pCriticalSection); |
| int iRet; |
| #ifdef _DEBUG |
| SIZE_T threadId; |
| |
| _ASSERTE(PalCsNotInitialized != pPalCriticalSection->cisInitState); |
| |
| threadId = ObtainCurrentThreadId(pThread); |
| _ASSERTE(threadId == pPalCriticalSection->OwningThread); |
| |
| if (0 >= pPalCriticalSection->RecursionCount) |
| DebugBreak(); |
| |
| _ASSERTE(0 < pPalCriticalSection->RecursionCount); |
| #endif // _DEBUG |
| |
| if (0 < --pPalCriticalSection->RecursionCount) |
| return; |
| |
| pPalCriticalSection->OwningThread = 0; |
| |
| iRet = pthread_mutex_unlock(&pPalCriticalSection->csndNativeData.mutex); |
| _ASSERTE(0 == iRet); |
| } |
| |
| /*++ |
| Function: |
| CorUnix::InternalTryEnterCriticalSection |
| |
| Tries to acquire a CS. It returns true on success, false if the CS is |
| locked by another thread |
| --*/ |
| #ifdef MUTEX_BASED_CSS |
| bool InternalTryEnterCriticalSection( |
| CPalThread * pThread, |
| PCRITICAL_SECTION pCriticalSection) |
| #else // MUTEX_BASED_CSS |
| bool MTX_InternalTryEnterCriticalSection( |
| CPalThread * pThread, |
| PCRITICAL_SECTION pCriticalSection) |
| #endif // MUTEX_BASED_CSS |
| { |
| PAL_CRITICAL_SECTION * pPalCriticalSection = |
| reinterpret_cast<PAL_CRITICAL_SECTION*>(pCriticalSection); |
| bool fRet; |
| SIZE_T threadId; |
| |
| _ASSERTE(PalCsNotInitialized != pPalCriticalSection->cisInitState); |
| |
| threadId = ObtainCurrentThreadId(pThread); |
| |
| /* check if the current thread already owns the criticalSection */ |
| if (pPalCriticalSection->OwningThread == threadId) |
| { |
| pPalCriticalSection->RecursionCount += 1; |
| fRet = true; |
| goto ITECS_exit; |
| } |
| |
| fRet = (0 == pthread_mutex_trylock(&pPalCriticalSection->csndNativeData.mutex)); |
| |
| if (fRet) |
| { |
| pPalCriticalSection->OwningThread = threadId; |
| pPalCriticalSection->RecursionCount = 1; |
| } |
| |
| ITECS_exit: |
| return fRet; |
| } |
| #endif // MUTEX_BASED_CSS || _DEBUG |
| } |