#if NET20 || NET30 || !NET_4_6 using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; //using System.Security.Permissions; using LinqInternal.Threading; namespace System.Threading { //[HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)] //[HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)] public class ReaderWriterLockSlim : IDisposable { /* Position of each bit isn't really important * but their relative order is */ private const int _rwReadBit = 3; /* These values are used to manipulate the corresponding flags in _rwlock field */ private const int _rwWait = 1; private const int _rwWaitUpgrade = 2; private const int _rwWrite = 4; private const int _rwRead = 8; /* Some explanations: this field is the central point of the lock and keep track of all the requests * that are being made. The 3 lowest bits are used as flag to track "destructive" lock entries * (i.e attempting to take the write lock with or without having acquired an upgradeable lock beforehand). * All the remaining bits are intepreted as the actual number of reader currently using the lock * (which mean the lock is limited to 2^29 concurrent readers but since it's a high number there * is no overflow safe guard to remain simple). */ private int _rwlock; private readonly LockRecursionPolicy _recursionPolicy; private readonly bool _noRecursion; private readonly AtomicBoolean _upgradableTaken = new AtomicBoolean(); /* These events are just here for the sake of having a CPU-efficient sleep * when the wait for acquiring the lock is too long */ private readonly ManualResetEventSlim _upgradableEvent = new ManualResetEventSlim(true); private readonly ManualResetEventSlim _writerDoneEvent = new ManualResetEventSlim(true); private readonly ManualResetEventSlim _readerDoneEvent = new ManualResetEventSlim(true); // This Stopwatch instance is used for all threads since .Elapsed is thread-safe private readonly static Stopwatch _stopwatch = Stopwatch.StartNew(); /* For performance sake, these numbers are manipulated via classic increment and * decrement operations and thus are (as hinted by MSDN) not meant to be precise */ private int _numReadWaiters, _numUpgradeWaiters, _numWriteWaiters; private bool _disposed; private static int _idPool = int.MinValue; private readonly int _id = Interlocked.Increment(ref _idPool); /* This dictionary is instanciated per thread for all existing ReaderWriterLockSlim instance. * Each instance is defined by an internal integer id value used as a key in the dictionary. * to avoid keeping unneeded reference to the instance and getting in the way of the GC. * Since there is no LockCookie type here, all the useful per-thread infos concerning each * instance are kept here. */ [ThreadStatic] private static Dictionary _currentThreadState; private readonly /* Rwls tries to use this array as much as possible to quickly retrieve the thread-local * informations so that it ends up being only an array lookup. When the number of thread * using the instance goes past the length of the array, the code fallback to the normal * dictionary */ ThreadLockState[] _fastStateCache = new ThreadLockState[64]; public ReaderWriterLockSlim() : this(LockRecursionPolicy.NoRecursion) { } public ReaderWriterLockSlim(LockRecursionPolicy recursionPolicy) { _recursionPolicy = recursionPolicy; _noRecursion = recursionPolicy == LockRecursionPolicy.NoRecursion; } public void EnterReadLock() { TryEnterReadLock(-1); } public bool TryEnterReadLock(int millisecondsTimeout) { var dummy = false; return TryEnterReadLock(millisecondsTimeout, ref dummy); } private bool TryEnterReadLock(int millisecondsTimeout, ref bool success) { var ctstate = CurrentThreadState; if (CheckState(ctstate, millisecondsTimeout, LockState.Read)) { ++ctstate.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 (ctstate.LockState.Has(LockState.Upgradable) || (!_noRecursion && ctstate.LockState.Has(LockState.Write))) { RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { Interlocked.Add(ref _rwlock, _rwRead); ctstate.LockState |= LockState.Read; ++ctstate.ReaderRecursiveCount; success = true; } return true; } _numReadWaiters++; int val; 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 { } finally { if (((val = 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 (val >> _rwReadBit == 1) { _readerDoneEvent.Reset(); } ctstate.LockState ^= LockState.Read; ++ctstate.ReaderRecursiveCount; --_numReadWaiters; success = true; } else { Interlocked.Add(ref _rwlock, -_rwRead); } } if (success) { return true; } _writerDoneEvent.Wait(ComputeTimeout(millisecondsTimeout, start)); } while (millisecondsTimeout == -1 || (_stopwatch.ElapsedMilliseconds - start) < millisecondsTimeout); --_numReadWaiters; return false; } public bool TryEnterReadLock(TimeSpan timeout) { return TryEnterReadLock(CheckTimeout(timeout)); } public void ExitReadLock() { RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { var ctstate = CurrentThreadState; if (!ctstate.LockState.Has(LockState.Read)) { throw new SynchronizationLockException("The current thread has not entered the lock in read mode"); } if (--ctstate.ReaderRecursiveCount == 0) { ctstate.LockState ^= LockState.Read; if (Interlocked.Add(ref _rwlock, -_rwRead) >> _rwReadBit == 0) { _readerDoneEvent.Set(); } } } } public void EnterWriteLock() { TryEnterWriteLock(-1); } public bool TryEnterWriteLock(int millisecondsTimeout) { var ctstate = CurrentThreadState; if (CheckState(ctstate, millisecondsTimeout, LockState.Write)) { ++ctstate.WriterRecursiveCount; return true; } ++_numWriteWaiters; var isUpgradable = ctstate.LockState.Has(LockState.Upgradable); 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 { } 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 { } finally { var toWrite = state + _rwWrite - (registered ? registration : 0); if (Interlocked.CompareExchange(ref _rwlock, toWrite, state) == state) { _writerDoneEvent.Reset(); ctstate.LockState ^= LockState.Write; ++ctstate.WriterRecursiveCount; --_numWriteWaiters; 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 { } 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); --_numWriteWaiters; } finally { if (registered) { Interlocked.Add(ref _rwlock, isUpgradable ? -_rwWaitUpgrade : -_rwWait); } } return false; } public bool TryEnterWriteLock(TimeSpan timeout) { return TryEnterWriteLock(CheckTimeout(timeout)); } public void ExitWriteLock() { RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { var ctstate = CurrentThreadState; if (!ctstate.LockState.Has(LockState.Write)) { throw new SynchronizationLockException("The current thread has not entered the lock in write mode"); } if (--ctstate.WriterRecursiveCount == 0) { var isUpgradable = ctstate.LockState.Has(LockState.Upgradable); ctstate.LockState ^= LockState.Write; var value = Interlocked.Add(ref _rwlock, isUpgradable ? _rwRead - _rwWrite : -_rwWrite); _writerDoneEvent.Set(); if (isUpgradable && value >> _rwReadBit == 1) { _readerDoneEvent.Reset(); } } } } public void EnterUpgradeableReadLock() { TryEnterUpgradeableReadLock(-1); } // // Taking the Upgradable read lock is like taking a read lock // but we limit it to a single upgradable at a time. // public bool TryEnterUpgradeableReadLock(int millisecondsTimeout) { var ctstate = CurrentThreadState; if (CheckState(ctstate, millisecondsTimeout, LockState.Upgradable)) { ++ctstate.UpgradeableRecursiveCount; return true; } if (ctstate.LockState.Has(LockState.Read)) { throw new LockRecursionException("The current thread has already entered read mode"); } ++_numUpgradeWaiters; var start = millisecondsTimeout == -1 ? 0 : _stopwatch.ElapsedMilliseconds; var taken = false; var success = false; // We first try to obtain the upgradeable right try { while (!_upgradableEvent.IsSet() || !taken) { try { } finally { taken = _upgradableTaken.TryRelaxedSet(); } if (taken) { break; } if (millisecondsTimeout != -1 && (_stopwatch.ElapsedMilliseconds - start) > millisecondsTimeout) { --_numUpgradeWaiters; return false; } _upgradableEvent.Wait(ComputeTimeout(millisecondsTimeout, start)); } _upgradableEvent.Reset(); RuntimeHelpers.PrepareConstrainedRegions(); try { // Then it's a simple reader lock acquiring TryEnterReadLock(ComputeTimeout(millisecondsTimeout, start), ref success); } finally { if (success) { ctstate.LockState |= LockState.Upgradable; ctstate.LockState &= ~LockState.Read; --ctstate.ReaderRecursiveCount; ++ctstate.UpgradeableRecursiveCount; } else { _upgradableTaken.Value = false; _upgradableEvent.Set(); } } --_numUpgradeWaiters; } catch (Exception ex) { GC.KeepAlive(ex); // An async exception occured, if we had taken the upgradable mode, release it _upgradableTaken.Value &= (!taken || success); } return success; } public bool TryEnterUpgradeableReadLock(TimeSpan timeout) { return TryEnterUpgradeableReadLock(CheckTimeout(timeout)); } public void ExitUpgradeableReadLock() { RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { var ctstate = CurrentThreadState; if (!ctstate.LockState.Has(LockState.Upgradable | LockState.Read)) { throw new SynchronizationLockException("The current thread has not entered the lock in upgradable mode"); } if (--ctstate.UpgradeableRecursiveCount == 0) { _upgradableTaken.Value = false; _upgradableEvent.Set(); ctstate.LockState &= ~LockState.Upgradable; if (Interlocked.Add(ref _rwlock, -_rwRead) >> _rwReadBit == 0) { _readerDoneEvent.Set(); } } } } public void Dispose() { Dispose(true); } private void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { if (IsReadLockHeld || IsUpgradeableReadLockHeld || IsWriteLockHeld) { throw new SynchronizationLockException("The lock is being disposed while still being used"); } _upgradableEvent.Dispose(); _writerDoneEvent.Dispose(); _readerDoneEvent.Dispose(); _disposed = true; } } public bool IsReadLockHeld { get { return _rwlock >= _rwRead && CurrentThreadState.LockState.Has(LockState.Read); } } public bool IsWriteLockHeld { get { return (_rwlock & _rwWrite) > 0 && CurrentThreadState.LockState.Has(LockState.Write); } } public bool IsUpgradeableReadLockHeld { get { return _upgradableTaken.Value && CurrentThreadState.LockState.Has(LockState.Upgradable); } } public int CurrentReadCount { get { return (_rwlock >> _rwReadBit) - (_upgradableTaken.Value ? 1 : 0); } } public int RecursiveReadCount { get { return CurrentThreadState.ReaderRecursiveCount; } } public int RecursiveUpgradeCount { get { return CurrentThreadState.UpgradeableRecursiveCount; } } public int RecursiveWriteCount { get { return CurrentThreadState.WriterRecursiveCount; } } public int WaitingReadCount { get { return _numReadWaiters; } } public int WaitingUpgradeCount { get { return _numUpgradeWaiters; } } public int WaitingWriteCount { get { return _numWriteWaiters; } } public LockRecursionPolicy RecursionPolicy { get { return _recursionPolicy; } } private ThreadLockState CurrentThreadState { get { var tid = Thread.CurrentThread.ManagedThreadId; return tid < _fastStateCache.Length ? _fastStateCache[tid] ?? (_fastStateCache[tid] = new ThreadLockState()) : GetGlobalThreadState(); } } private ThreadLockState GetGlobalThreadState() { if (_currentThreadState == null) { Interlocked.CompareExchange(ref _currentThreadState, new Dictionary(), null); } ThreadLockState state; if (!_currentThreadState.TryGetValue(_id, out state)) { _currentThreadState[_id] = state = new ThreadLockState(); } return state; } private bool CheckState(ThreadLockState state, int millisecondsTimeout, LockState validState) { if (_disposed) { throw new ObjectDisposedException("ReaderWriterLockSlim"); } if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException("millisecondsTimeout"); } // Detect and prevent recursion var ctstate = state.LockState; if (ctstate != LockState.None && _noRecursion && (!ctstate.Has(LockState.Upgradable) || validState == LockState.Upgradable)) { throw new LockRecursionException("The current thread has already a lock and recursion isn't supported"); } if (_noRecursion) { return false; } // If we already had right lock state, just return if (ctstate.Has(validState)) { return true; } // In read mode you can just enter Read recursively if (ctstate == LockState.Read) { throw new LockRecursionException(); } return false; } private static int CheckTimeout(TimeSpan timeout) { try { return checked((int)timeout.TotalMilliseconds); } catch (OverflowException) { throw new ArgumentOutOfRangeException("timeout"); } } private static int ComputeTimeout(int millisecondsTimeout, long start) { return millisecondsTimeout == -1 ? -1 : (int)Math.Max(_stopwatch.ElapsedMilliseconds - start - millisecondsTimeout, 1); } } } #endif