public static void Release(CctorHandle cctor) { using (LockHolder.Hold(s_cctorGlobalLock)) { Cctor[] cctors = cctor.Array; int cctorIndex = cctor.Index; if (0 == Interlocked.Decrement(ref cctors[cctorIndex]._refCount)) { if (cctors[cctorIndex].Exception == null) { cctors[cctorIndex] = default; s_count--; } } } }
public static void Release(CctorHandle cctor) { s_cctorGlobalLock.Acquire(); try { Cctor[] cctors = cctor.Array; int cctorIndex = cctor.Index; if (0 == Interlocked.Decrement(ref cctors[cctorIndex]._refCount)) { if (cctors[cctorIndex].Exception == null) { cctors[cctorIndex] = new Cctor(); s_count--; } } } finally { s_cctorGlobalLock.Release(); } }
public static int MarkThreadAsBlocked(int managedThreadId, CctorHandle blockedOn) { #if DEBUG const int Grow = 2; #else const int Grow = 10; #endif using (LockHolder.Hold(s_cctorGlobalLock)) { if (s_blockingRecords == null) { s_blockingRecords = new BlockingRecord[Grow]; } int found; for (found = 0; found < s_nextBlockingRecordIndex; found++) { if (s_blockingRecords[found].ManagedThreadId == managedThreadId) { break; } } if (found == s_nextBlockingRecordIndex) { if (s_nextBlockingRecordIndex == s_blockingRecords.Length) { BlockingRecord[] newBlockingRecords = new BlockingRecord[s_blockingRecords.Length + Grow]; for (int i = 0; i < s_blockingRecords.Length; i++) { newBlockingRecords[i] = s_blockingRecords[i]; } s_blockingRecords = newBlockingRecords; } s_blockingRecords[s_nextBlockingRecordIndex].ManagedThreadId = managedThreadId; s_nextBlockingRecordIndex++; } s_blockingRecords[found].BlockedOn = blockedOn; return(found); } }
public static int MarkThreadAsBlocked(int managedThreadId, CctorHandle blockedOn) { #if DEBUG const int Grow = 2; #else const int Grow = 10; #endif lock (s_cctorGlobalLock) { if (s_blockingRecords == null) s_blockingRecords = new BlockingRecord[Grow]; int found; for (found = 0; found < s_nextBlockingRecordIndex; found++) { if (s_blockingRecords[found].ManagedThreadId == managedThreadId) break; } if (found == s_nextBlockingRecordIndex) { if (s_nextBlockingRecordIndex == s_blockingRecords.Length) { BlockingRecord[] newBlockingRecords = new BlockingRecord[s_blockingRecords.Length + Grow]; for (int i = 0; i < s_blockingRecords.Length; i++) { newBlockingRecords[i] = s_blockingRecords[i]; } s_blockingRecords = newBlockingRecords; } s_blockingRecords[s_nextBlockingRecordIndex].ManagedThreadId = managedThreadId; s_nextBlockingRecordIndex++; } s_blockingRecords[found].BlockedOn = blockedOn; return found; } }
//========================================================================================================= // Return value: // true - lock acquired. // false - deadlock detected. Lock not acquired. //========================================================================================================= private static bool DeadlockAwareAcquire(CctorHandle cctor, IntPtr pfnCctor) { const int WaitIntervalSeedInMS = 1; // seed with 1ms and double every time through the loop const int WaitIntervalLimitInMS = WaitIntervalSeedInMS << 7; // limit of 128ms int waitIntervalInMS = WaitIntervalSeedInMS; int cctorIndex = cctor.Index; Cctor[] cctors = cctor.Array; Lock lck = cctors[cctorIndex].Lock; if (lck.IsAcquired) return false; // Thread recursively triggered the same cctor. if (lck.TryAcquire(waitIntervalInMS)) return true; // We couldn't acquire the lock. See if this .cctor is involved in a cross-thread deadlock. If so, break // the deadlock by breaking the guarantee - we'll skip running the .cctor and let the caller take his chances. int currentManagedThreadId = CurrentManagedThreadId; int unmarkCookie = -1; try { // We'll spin in a forever-loop of checking for a deadlock state, then waiting a short time, then // checking for a deadlock state again, and so on. This is because the BlockedRecord info has a built-in // lag time - threads don't report themselves as blocking until they've been blocked for a non-trivial // amount of time. // // If the threads are deadlocked for any reason other a class constructor cycling, this loop will never // terminate - this is by design. If the user code inside the class constructors were to // deadlock themselves, then that's a bug in user code. for (;;) { lock (s_cctorGlobalLock) { // Ask the guy who holds the cctor lock we're trying to acquire who he's waiting for. Keep // walking down that chain until we either discover a cycle or reach a non-blocking state. Note // that reaching a non-blocking state is not proof that we've avoided a deadlock due to the // BlockingRecord reporting lag. CctorHandle cctorWalk = cctor; int chainStepCount = 0; for (; chainStepCount < Cctor.Count; chainStepCount++) { int cctorWalkIndex = cctorWalk.Index; Cctor[] cctorWalkArray = cctorWalk.Array; int holdingThread = cctorWalkArray[cctorWalkIndex].HoldingThread; if (holdingThread == currentManagedThreadId) { // Deadlock detected. We will break the guarantee and return without running the .cctor. DebugLog("A class constructor was skipped due to class constructor cycle. cctor={0}, thread={1}", pfnCctor, currentManagedThreadId); // We are maintaining an invariant that the BlockingRecords never show a cycle because, // before we add a record, we first check for a cycle. As a result, once we've said // we're waiting, we are committed to waiting and will not need to skip running this // .cctor. Debug.Assert(unmarkCookie == -1); return false; } if (holdingThread == ManagedThreadIdNone) { // No one appears to be holding this cctor lock. Give the current thread some more time // to acquire the lock. break; } cctorWalk = BlockingRecord.GetCctorThatThreadIsBlockedOn(holdingThread); if (cctorWalk.Array == null) { // The final thread in the chain appears to be blocked on nothing. Give the current // thread some more time to acquire the lock. break; } } // We don't allow cycles in the BlockingRecords, so we must always enumerate at most each entry, // but never more. Debug.Assert(chainStepCount < Cctor.Count); // We have not discovered a deadlock, so let's register the fact that we're waiting on another // thread and continue to wait. It is important that we only signal that we are blocked after // we check for a deadlock because, otherwise, we give all threads involved in the deadlock the // opportunity to break it themselves and that leads to "ping-ponging" between the cctors // involved in the cycle, allowing intermediate cctor results to be observed. // // The invariant here is that we never 'publish' a BlockingRecord that forms a cycle. So it is // important that the look-for-cycle-and-then-publish-wait-status operation be atomic with // respect to other updates to the BlockingRecords. if (unmarkCookie == -1) { NoisyLog("Mark thread blocked, cctor={0}, thread={1}", pfnCctor, currentManagedThreadId); unmarkCookie = BlockingRecord.MarkThreadAsBlocked(currentManagedThreadId, cctor); } } // _cctorGlobalLock scope if (waitIntervalInMS < WaitIntervalLimitInMS) waitIntervalInMS *= 2; // We didn't find a cycle yet, try to take the lock again. if (lck.TryAcquire(waitIntervalInMS)) return true; } // infinite loop } finally { if (unmarkCookie != -1) { NoisyLog("Unmark thread blocked, cctor={0}, thread={1}", pfnCctor, currentManagedThreadId); BlockingRecord.UnmarkThreadAsBlocked(unmarkCookie); } } }
public static unsafe void EnsureClassConstructorRun(StaticClassConstructionContext *pContext) { IntPtr pfnCctor = pContext->cctorMethodAddress; NoisyLog("EnsureClassConstructorRun, cctor={0}, thread={1}", pfnCctor, CurrentManagedThreadId); // If we were called from MRT, this check is redundant but harmless. This is in case someone within classlib // (cough, Reflection) needs to call this explicitly. if (pContext->initialized == 1) { NoisyLog("Cctor already run, cctor={0}, thread={1}", pfnCctor, CurrentManagedThreadId); return; } CctorHandle cctor = Cctor.GetCctor(pContext); Cctor[] cctors = cctor.Array; int cctorIndex = cctor.Index; try { Lock cctorLock = cctors[cctorIndex].Lock; if (DeadlockAwareAcquire(cctor, pfnCctor)) { int currentManagedThreadId = CurrentManagedThreadId; try { NoisyLog("Acquired cctor lock, cctor={0}, thread={1}", pfnCctor, currentManagedThreadId); cctors[cctorIndex].HoldingThread = currentManagedThreadId; if (pContext->initialized == 0) // Check again in case some thread raced us while we were acquiring the lock. { TypeInitializationException priorException = cctors[cctorIndex].Exception; if (priorException != null) { throw priorException; } try { NoisyLog("Calling cctor, cctor={0}, thread={1}", pfnCctor, currentManagedThreadId); ((delegate * < void >)pfnCctor)(); // Insert a memory barrier here to order any writes executed as part of static class // construction above with respect to the initialized flag update we're about to make // below. This is important since the fast path for checking the cctor uses a normal read // and doesn't come here so without the barrier it could observe initialized == 1 but // still see uninitialized static fields on the class. Interlocked.MemoryBarrier(); NoisyLog("Set type inited, cctor={0}, thread={1}", pfnCctor, currentManagedThreadId); pContext->initialized = 1; } catch (Exception e) { TypeInitializationException wrappedException = new TypeInitializationException(null, SR.TypeInitialization_Type_NoTypeAvailable, e); cctors[cctorIndex].Exception = wrappedException; throw wrappedException; } } } finally { cctors[cctorIndex].HoldingThread = ManagedThreadIdNone; NoisyLog("Releasing cctor lock, cctor={0}, thread={1}", pfnCctor, currentManagedThreadId); cctorLock.Release(); } } else { // Cctor cycle resulted in a deadlock. We will break the guarantee and return without running the // .cctor. } } finally { Cctor.Release(cctor); } NoisyLog("EnsureClassConstructorRun complete, cctor={0}, thread={1}", pfnCctor, CurrentManagedThreadId); }
//========================================================================================================= // Return value: // true - lock acquired. // false - deadlock detected. Lock not acquired. //========================================================================================================= private static bool DeadlockAwareAcquire(CctorHandle cctor, IntPtr pfnCctor) { const int WaitIntervalSeedInMS = 1; // seed with 1ms and double every time through the loop const int WaitIntervalLimitInMS = WaitIntervalSeedInMS << 7; // limit of 128ms int waitIntervalInMS = WaitIntervalSeedInMS; int cctorIndex = cctor.Index; Cctor[] cctors = cctor.Array; Lock lck = cctors[cctorIndex].Lock; if (lck.IsAcquired) { return(false); // Thread recursively triggered the same cctor. } if (lck.TryAcquire(waitIntervalInMS)) { return(true); } // We couldn't acquire the lock. See if this .cctor is involved in a cross-thread deadlock. If so, break // the deadlock by breaking the guarantee - we'll skip running the .cctor and let the caller take his chances. int currentManagedThreadId = CurrentManagedThreadId; int unmarkCookie = -1; try { // We'll spin in a forever-loop of checking for a deadlock state, then waiting a short time, then // checking for a deadlock state again, and so on. This is because the BlockedRecord info has a built-in // lag time - threads don't report themselves as blocking until they've been blocked for a non-trivial // amount of time. // // If the threads are deadlocked for any reason other a class constructor cycling, this loop will never // terminate - this is by design. If the user code inside the class constructors were to // deadlock themselves, then that's a bug in user code. for (;;) { using (LockHolder.Hold(s_cctorGlobalLock)) { // Ask the guy who holds the cctor lock we're trying to acquire who he's waiting for. Keep // walking down that chain until we either discover a cycle or reach a non-blocking state. Note // that reaching a non-blocking state is not proof that we've avoided a deadlock due to the // BlockingRecord reporting lag. CctorHandle cctorWalk = cctor; int chainStepCount = 0; for (; chainStepCount < Cctor.Count; chainStepCount++) { int cctorWalkIndex = cctorWalk.Index; Cctor[] cctorWalkArray = cctorWalk.Array; int holdingThread = cctorWalkArray[cctorWalkIndex].HoldingThread; if (holdingThread == currentManagedThreadId) { // Deadlock detected. We will break the guarantee and return without running the .cctor. DebugLog("A class constructor was skipped due to class constructor cycle. cctor={0}, thread={1}", pfnCctor, currentManagedThreadId); // We are maintaining an invariant that the BlockingRecords never show a cycle because, // before we add a record, we first check for a cycle. As a result, once we've said // we're waiting, we are committed to waiting and will not need to skip running this // .cctor. Debug.Assert(unmarkCookie == -1); return(false); } if (holdingThread == ManagedThreadIdNone) { // No one appears to be holding this cctor lock. Give the current thread some more time // to acquire the lock. break; } cctorWalk = BlockingRecord.GetCctorThatThreadIsBlockedOn(holdingThread); if (cctorWalk.Array == null) { // The final thread in the chain appears to be blocked on nothing. Give the current // thread some more time to acquire the lock. break; } } // We don't allow cycles in the BlockingRecords, so we must always enumerate at most each entry, // but never more. Debug.Assert(chainStepCount < Cctor.Count); // We have not discovered a deadlock, so let's register the fact that we're waiting on another // thread and continue to wait. It is important that we only signal that we are blocked after // we check for a deadlock because, otherwise, we give all threads involved in the deadlock the // opportunity to break it themselves and that leads to "ping-ponging" between the cctors // involved in the cycle, allowing intermediate cctor results to be observed. // // The invariant here is that we never 'publish' a BlockingRecord that forms a cycle. So it is // important that the look-for-cycle-and-then-publish-wait-status operation be atomic with // respect to other updates to the BlockingRecords. if (unmarkCookie == -1) { NoisyLog("Mark thread blocked, cctor={0}, thread={1}", pfnCctor, currentManagedThreadId); unmarkCookie = BlockingRecord.MarkThreadAsBlocked(currentManagedThreadId, cctor); } } // _cctorGlobalLock scope if (waitIntervalInMS < WaitIntervalLimitInMS) { waitIntervalInMS *= 2; } // We didn't find a cycle yet, try to take the lock again. if (lck.TryAcquire(waitIntervalInMS)) { return(true); } } // infinite loop } finally { if (unmarkCookie != -1) { NoisyLog("Unmark thread blocked, cctor={0}, thread={1}", pfnCctor, currentManagedThreadId); BlockingRecord.UnmarkThreadAsBlocked(unmarkCookie); } } }
public static void Release(CctorHandle cctor) { using (LockHolder.Hold(s_cctorGlobalLock)) { Cctor[] cctors = cctor.Array; int cctorIndex = cctor.Index; if (0 == Interlocked.Decrement(ref cctors[cctorIndex]._refCount)) { if (cctors[cctorIndex].Exception == null) { cctors[cctorIndex] = new Cctor(); s_count--; } } } }