private bool TryAcquireContended(IntPtr currentThreadId, int millisecondsTimeout, bool trackContentions = false) { // // If we already own the lock, just increment the recursion count. // if (_owningThreadId == currentThreadId) { checked { _recursionCount++; } return(true); } // // We've already made one lock attempt at this point, so bail early if the timeout is zero. // if (millisecondsTimeout == 0) { return(false); } int spins = 1; if (s_maxSpinCount == SpinningNotInitialized) { // Use RhGetProcessCpuCount directly to avoid Environment.ProcessorCount->ClassConstructorRunner->Lock->Environment.ProcessorCount cycle s_maxSpinCount = (RuntimeImports.RhGetProcessCpuCount() > 1) ? MaxSpinningValue : SpinningDisabled; } while (true) { // // Try to grab the lock. We may take the lock here even if there are existing waiters. This creates the possibility // of starvation of waiters, but it also prevents lock convoys from destroying perf. // The starvation issue is largely mitigated by the priority boost the OS gives to a waiter when we set // the event, after we release the lock. Eventually waiters will be boosted high enough to preempt this thread. // int oldState = _state; if ((oldState & Locked) == 0 && Interlocked.CompareExchange(ref _state, oldState | Locked, oldState) == oldState) { goto GotTheLock; } // // Back off by a factor of 2 for each attempt, up to MaxSpinCount // if (spins <= s_maxSpinCount) { RuntimeImports.RhSpinWait(spins); spins *= 2; } else if (oldState != 0) { // // We reached our spin limit, and need to wait. Increment the waiter count. // Note that we do not do any overflow checking on this increment. In order to overflow, // we'd need to have about 1 billion waiting threads, which is inconceivable anytime in the // forseeable future. // int newState = (oldState + WaiterCountIncrement) & ~WaiterWoken; if (Interlocked.CompareExchange(ref _state, newState, oldState) == oldState) { break; } } } // // Now we wait. // if (trackContentions) { Monitor.IncrementLockContentionCount(); } TimeoutTracker timeoutTracker = TimeoutTracker.Start(millisecondsTimeout); AutoResetEvent ev = Event; while (true) { Debug.Assert(_state >= WaiterCountIncrement); bool waitSucceeded = ev.WaitOne(timeoutTracker.Remaining); while (true) { int oldState = _state; Debug.Assert(oldState >= WaiterCountIncrement); // Clear the "waiter woken" bit. int newState = oldState & ~WaiterWoken; if ((oldState & Locked) == 0) { // The lock is available, try to get it. newState |= Locked; newState -= WaiterCountIncrement; if (Interlocked.CompareExchange(ref _state, newState, oldState) == oldState) { goto GotTheLock; } } else if (!waitSucceeded) { // The lock is not available, and we timed out. We're not going to wait agin. newState -= WaiterCountIncrement; if (Interlocked.CompareExchange(ref _state, newState, oldState) == oldState) { return(false); } } else { // The lock is not available, and we didn't time out. We're going to wait again. if (Interlocked.CompareExchange(ref _state, newState, oldState) == oldState) { break; } } } } GotTheLock: Debug.Assert((_state | Locked) != 0); Debug.Assert(_owningThreadId == IntPtr.Zero); Debug.Assert(_recursionCount == 0); _owningThreadId = currentThreadId; return(true); }