You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
679 lines
23 KiB
679 lines
23 KiB
#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<int, ThreadLockState> _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<int, ThreadLockState>(), 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 |