public bool TryEnterWriteLock(int millisecondsTimeout) { var currentThreadState = CurrentThreadState; if (CheckState(currentThreadState, millisecondsTimeout, LockState.Write)) { ++currentThreadState.WriterRecursiveCount; return(true); } ++WaitingWriteCount; var isUpgradable = (currentThreadState.LockState & LockState.Upgradable) > 0; var registered = false; var success = false; RuntimeHelpers.PrepareConstrainedRegions(); try { /* If the code goes there that means we had a read lock beforehand * that need to be suppressed, we also take the opportunity to register * our interest in the write lock to avoid other write wannabe process * coming in the middle */ if (isUpgradable && _rwLock >= _rwRead) { try { // Empty } finally { if (Interlocked.Add(ref _rwLock, _rwWaitUpgrade - _rwRead) >> _rwReadBit == 0) { _readerDoneEvent.Set(); } registered = true; } } var stateCheck = isUpgradable ? _rwWaitUpgrade + _rwWait : _rwWait; var start = millisecondsTimeout == -1 ? 0 : _stopwatch.ElapsedMilliseconds; var registration = isUpgradable ? _rwWaitUpgrade : _rwWait; do { var state = _rwLock; if (state <= stateCheck) { try { // Empty } finally { var toWrite = state + _rwWrite - (registered ? registration : 0); if (Interlocked.CompareExchange(ref _rwLock, toWrite, state) == state) { _writerDoneEvent.Reset(); currentThreadState.LockState ^= LockState.Write; ++currentThreadState.WriterRecursiveCount; --WaitingWriteCount; registered = false; success = true; } } if (success) { return(true); } } state = _rwLock; // We register our interest in taking the Write lock (if upgradeable it's already done) if (!isUpgradable) { while ((state & _rwWait) == 0) { try { // Empty } finally { registered |= Interlocked.CompareExchange(ref _rwLock, state | _rwWait, state) == state; } if (registered) { break; } state = _rwLock; } } // Before falling to sleep do { if (_rwLock <= stateCheck) { break; } if ((_rwLock & _rwWrite) != 0) { _writerDoneEvent.Wait(ComputeTimeout(millisecondsTimeout, start)); } else if (_rwLock >> _rwReadBit > 0) { _readerDoneEvent.Wait(ComputeTimeout(millisecondsTimeout, start)); } } while (millisecondsTimeout < 0 || _stopwatch.ElapsedMilliseconds - start < millisecondsTimeout); } while (millisecondsTimeout < 0 || _stopwatch.ElapsedMilliseconds - start < millisecondsTimeout); --WaitingWriteCount; } finally { if (registered) { Interlocked.Add(ref _rwLock, isUpgradable ? -_rwWaitUpgrade : -_rwWait); } } return(false); }
private bool TryEnterReadLock(int millisecondsTimeout, ref bool success) { var currentThreadState = CurrentThreadState; if (CheckState(currentThreadState, millisecondsTimeout, LockState.Read)) { ++currentThreadState.ReaderRecursiveCount; return(true); } // This is downgrading from upgradable, no need for check since // we already have a sort-of read lock that's going to disappear // after user calls ExitUpgradeableReadLock. // Same idea when recursion is allowed and a write thread wants to // go for a Read too. if ((currentThreadState.LockState & LockState.Upgradable) > 0 || (!_noRecursion && (currentThreadState.LockState & LockState.Write) > 0)) { RuntimeHelpers.PrepareConstrainedRegions(); try { // Empty } finally { Interlocked.Add(ref _rwLock, _rwRead); currentThreadState.LockState |= LockState.Read; ++currentThreadState.ReaderRecursiveCount; success = true; } return(true); } WaitingReadCount++; var start = millisecondsTimeout == -1 ? 0 : _stopwatch.ElapsedMilliseconds; do { /* Check if a writer is present (RwWrite) or if there is someone waiting to * acquire a writer lock in the queue (RwWait | RwWaitUpgrade). */ if ((_rwLock & (_rwWrite | _rwWait | _rwWaitUpgrade)) > 0) { _writerDoneEvent.Wait(ComputeTimeout(millisecondsTimeout, start)); continue; } /* Optimistically try to add ourselves to the reader value * if the adding was too late and another writer came in between * we revert the operation. */ RuntimeHelpers.PrepareConstrainedRegions(); try { // Empty } finally { int add; if (((add = Interlocked.Add(ref _rwLock, _rwRead)) & (_rwWrite | _rwWait | _rwWaitUpgrade)) == 0) { /* If we are the first reader, reset the event to let other threads * sleep correctly if they try to acquire write lock */ if (add >> _rwReadBit == 1) { _readerDoneEvent.Reset(); } currentThreadState.LockState ^= LockState.Read; ++currentThreadState.ReaderRecursiveCount; --WaitingReadCount; success = true; } else { Interlocked.Add(ref _rwLock, -_rwRead); } } if (success) { return(true); } _writerDoneEvent.Wait(ComputeTimeout(millisecondsTimeout, start)); } while (millisecondsTimeout == -1 || _stopwatch.ElapsedMilliseconds - start < millisecondsTimeout); --WaitingReadCount; return(false); }
/// <summary> /// Try acquire the lock with long path, this is usually called after the first path in Enter and /// TryEnter failed The reason for short path is to make it inline in the run time which improves the /// performance. This method assumed that the parameter are validated in Enter or TryEnter method. /// </summary> /// <param name="millisecondsTimeout">The timeout milliseconds</param> /// <param name="lockTaken">The lockTaken param</param> private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken) { // The fast path doesn't throw any exception, so we have to validate the parameters here if (lockTaken) { lockTaken = false; throw new ArgumentException(SR.SpinLock_TryReliableEnter_ArgumentException); } if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException( nameof(millisecondsTimeout), millisecondsTimeout, SR.SpinLock_TryEnter_ArgumentOutOfRange); } uint startTime = 0; if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0) { startTime = TimeoutHelper.GetTime(); } if (IsThreadOwnerTrackingEnabled) { // Slow path for enabled thread tracking mode ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTime, ref lockTaken); return; } // then thread tracking is disabled // In this case there are three ways to acquire the lock // 1- the first way the thread either tries to get the lock if it's free or updates the waiters, if the turn >= the processors count then go to 3 else go to 2 // 2- In this step the waiter threads spins and tries to acquire the lock, the number of spin iterations and spin count is dependent on the thread turn // the late the thread arrives the more it spins and less frequent it check the lock availability // Also the spins count is increases each iteration // If the spins iterations finished and failed to acquire the lock, go to step 3 // 3- This is the yielding step, there are two ways of yielding Thread.Yield and Sleep(1) // If the timeout is expired in after step 1, we need to decrement the waiters count before returning int observedOwner; int turn = int.MaxValue; // ***Step 1, take the lock or update the waiters // try to acquire the lock directly if possible or update the waiters count observedOwner = _owner; if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED) { if (CompareExchange(ref _owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner) { // Acquired lock return; } if (millisecondsTimeout == 0) { // Did not acquire lock in CompareExchange and timeout is 0 so fail fast return; } } else if (millisecondsTimeout == 0) { // Did not acquire lock as owned and timeout is 0 so fail fast return; } else // failed to acquire the lock, then try to update the waiters. If the waiters count reached the maximum, just break the loop to avoid overflow { if ((observedOwner & WAITERS_MASK) != MAXIMUM_WAITERS) { // This can still overflow, but maybe there will never be that many waiters turn = (Interlocked.Add(ref _owner, 2) & WAITERS_MASK) >> 1; } } // lock acquired failed and waiters updated // *** Step 2, Spinning and Yielding SpinWait spinner = default; if (turn > Environment.ProcessorCount) { spinner.Count = SpinWait.YieldThreshold; } while (true) { spinner.SpinOnce(SLEEP_ONE_FREQUENCY); observedOwner = _owner; if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED) { int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero observedOwner | 1 // don't decrement it. just set the lock bit, it is zero because a previous call of Exit(false) which corrupted the waiters : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit Debug.Assert((newOwner & WAITERS_MASK) >= 0); if (CompareExchange(ref _owner, newOwner, observedOwner, ref lockTaken) == observedOwner) { return; } } if (spinner.Count % TIMEOUT_CHECK_FREQUENCY == 0) { // Check the timeout. if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0) { DecrementWaiters(); return; } } } }