| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- using Internal.Runtime.Augments;
- using System.Diagnostics; // for TraceInformation
- using System.Runtime.CompilerServices;
- namespace System.Threading
- {
- public enum LockRecursionPolicy
- {
- NoRecursion = 0,
- SupportsRecursion = 1,
- }
- //
- // ReaderWriterCount tracks how many of each kind of lock is held by each thread.
- // We keep a linked list for each thread, attached to a ThreadStatic field.
- // These are reused wherever possible, so that a given thread will only
- // allocate N of these, where N is the maximum number of locks held simultaneously
- // by that thread.
- //
- internal class ReaderWriterCount
- {
- // Which lock does this object belong to? This is a numeric ID for two reasons:
- // 1) We don't want this field to keep the lock object alive, and a WeakReference would
- // be too expensive.
- // 2) Setting the value of a long is faster than setting the value of a reference.
- // The "hot" paths in ReaderWriterLockSlim are short enough that this actually
- // matters.
- public long lockID;
- // How many reader locks does this thread hold on this ReaderWriterLockSlim instance?
- public int readercount;
- // Ditto for writer/upgrader counts. These are only used if the lock allows recursion.
- // But we have to have the fields on every ReaderWriterCount instance, because
- // we reuse it for different locks.
- public int writercount;
- public int upgradecount;
- // Next RWC in this thread's list.
- public ReaderWriterCount next;
- }
- /// <summary>
- /// A reader-writer lock implementation that is intended to be simple, yet very
- /// efficient. In particular only 1 interlocked operation is taken for any lock
- /// operation (we use spin locks to achieve this). The spin lock is never held
- /// for more than a few instructions (in particular, we never call event APIs
- /// or in fact any non-trivial API while holding the spin lock).
- /// </summary>
- public class ReaderWriterLockSlim : IDisposable
- {
- private static readonly int ProcessorCount = Environment.ProcessorCount;
- //Specifying if the lock can be reacquired recursively.
- private readonly bool _fIsReentrant;
- // Lock specification for _spinLock: This lock protects exactly the local fields associated with this
- // instance of ReaderWriterLockSlim. It does NOT protect the memory associated with
- // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
- SpinLock _spinLock;
- // These variables allow use to avoid Setting events (which is expensive) if we don't have to.
- private uint _numWriteWaiters; // maximum number of threads that can be doing a WaitOne on the writeEvent
- private uint _numReadWaiters; // maximum number of threads that can be doing a WaitOne on the readEvent
- private uint _numWriteUpgradeWaiters; // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1).
- private uint _numUpgradeWaiters;
- private WaiterStates _waiterStates;
- private int _upgradeLockOwnerId;
- private int _writeLockOwnerId;
- // conditions we wait on.
- private EventWaitHandle _writeEvent; // threads waiting to acquire a write lock go here.
- private EventWaitHandle _readEvent; // threads waiting to acquire a read lock go here (will be released in bulk)
- private EventWaitHandle _upgradeEvent; // thread waiting to acquire the upgrade lock
- private EventWaitHandle _waitUpgradeEvent; // thread waiting to upgrade from the upgrade lock to a write lock go here (at most one)
- // Every lock instance has a unique ID, which is used by ReaderWriterCount to associate itself with the lock
- // without holding a reference to it.
- private static long s_nextLockID;
- private long _lockID;
- // See comments on ReaderWriterCount.
- [ThreadStatic]
- private static ReaderWriterCount t_rwc;
- private bool _fUpgradeThreadHoldingRead;
- private const int MaxSpinCount = 20;
- //The uint, that contains info like if the writer lock is held, num of
- //readers etc.
- private uint _owners;
- //Various R/W masks
- //Note:
- //The Uint is divided as follows:
- //
- //Writer-Owned Waiting-Writers Waiting Upgraders Num-Readers
- // 31 30 29 28.......0
- //
- //Dividing the uint, allows to vastly simplify logic for checking if a
- //reader should go in etc. Setting the writer bit will automatically
- //make the value of the uint much larger than the max num of readers
- //allowed, thus causing the check for max_readers to fail.
- private const uint WRITER_HELD = 0x80000000;
- private const uint WAITING_WRITERS = 0x40000000;
- private const uint WAITING_UPGRADER = 0x20000000;
- //The max readers is actually one less then its theoretical max.
- //This is done in order to prevent reader count overflows. If the reader
- //count reaches max, other readers will wait.
- private const uint MAX_READER = 0x10000000 - 2;
- private const uint READER_MASK = 0x10000000 - 1;
- private bool _fDisposed;
- private void InitializeThreadCounts()
- {
- _upgradeLockOwnerId = -1;
- _writeLockOwnerId = -1;
- }
- public ReaderWriterLockSlim()
- : this(LockRecursionPolicy.NoRecursion)
- {
- }
- public ReaderWriterLockSlim(LockRecursionPolicy recursionPolicy)
- {
- if (recursionPolicy == LockRecursionPolicy.SupportsRecursion)
- {
- _fIsReentrant = true;
- }
- InitializeThreadCounts();
- _waiterStates = WaiterStates.NoWaiters;
- _lockID = Interlocked.Increment(ref s_nextLockID);
- }
- private bool HasNoWaiters
- {
- get
- {
- #if DEBUG
- Debug.Assert(_spinLock.IsHeld);
- #endif
- return (_waiterStates & WaiterStates.NoWaiters) != WaiterStates.None;
- }
- set
- {
- #if DEBUG
- Debug.Assert(_spinLock.IsHeld);
- #endif
- if (value)
- {
- _waiterStates |= WaiterStates.NoWaiters;
- }
- else
- {
- _waiterStates &= ~WaiterStates.NoWaiters;
- }
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsRWEntryEmpty(ReaderWriterCount rwc)
- {
- if (rwc.lockID == 0)
- return true;
- else if (rwc.readercount == 0 && rwc.writercount == 0 && rwc.upgradecount == 0)
- return true;
- else
- return false;
- }
- private bool IsRwHashEntryChanged(ReaderWriterCount lrwc)
- {
- return lrwc.lockID != _lockID;
- }
- /// <summary>
- /// This routine retrieves/sets the per-thread counts needed to enforce the
- /// various rules related to acquiring the lock.
- ///
- /// DontAllocate is set to true if the caller just wants to get an existing
- /// entry for this thread, but doesn't want to add one if an existing one
- /// could not be found.
- /// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private ReaderWriterCount GetThreadRWCount(bool dontAllocate)
- {
- ReaderWriterCount rwc = t_rwc;
- ReaderWriterCount empty = null;
- while (rwc != null)
- {
- if (rwc.lockID == _lockID)
- return rwc;
- if (!dontAllocate && empty == null && IsRWEntryEmpty(rwc))
- empty = rwc;
- rwc = rwc.next;
- }
- if (dontAllocate)
- return null;
- if (empty == null)
- {
- empty = new ReaderWriterCount();
- empty.next = t_rwc;
- t_rwc = empty;
- }
- empty.lockID = _lockID;
- return empty;
- }
- public void EnterReadLock()
- {
- TryEnterReadLock(-1);
- }
- //
- // Common timeout support
- //
- private struct TimeoutTracker
- {
- private int _total;
- private int _start;
- public TimeoutTracker(TimeSpan timeout)
- {
- long ltm = (long)timeout.TotalMilliseconds;
- if (ltm < -1 || ltm > (long)int.MaxValue)
- throw new ArgumentOutOfRangeException(nameof(timeout));
- _total = (int)ltm;
- if (_total != -1 && _total != 0)
- _start = Environment.TickCount;
- else
- _start = 0;
- }
- public TimeoutTracker(int millisecondsTimeout)
- {
- if (millisecondsTimeout < -1)
- throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout));
- _total = millisecondsTimeout;
- if (_total != -1 && _total != 0)
- _start = Environment.TickCount;
- else
- _start = 0;
- }
- public int RemainingMilliseconds
- {
- get
- {
- if (_total == -1 || _total == 0)
- return _total;
- int elapsed = Environment.TickCount - _start;
- // elapsed may be negative if TickCount has overflowed by 2^31 milliseconds.
- if (elapsed < 0 || elapsed >= _total)
- return 0;
- return _total - elapsed;
- }
- }
- public bool IsExpired
- {
- get
- {
- return RemainingMilliseconds == 0;
- }
- }
- }
- public bool TryEnterReadLock(TimeSpan timeout)
- {
- return TryEnterReadLock(new TimeoutTracker(timeout));
- }
- public bool TryEnterReadLock(int millisecondsTimeout)
- {
- return TryEnterReadLock(new TimeoutTracker(millisecondsTimeout));
- }
- private bool TryEnterReadLock(TimeoutTracker timeout)
- {
- return TryEnterReadLockCore(timeout);
- }
- private bool TryEnterReadLockCore(TimeoutTracker timeout)
- {
- if (_fDisposed)
- throw new ObjectDisposedException(null);
- ReaderWriterCount lrwc = null;
- int id = Environment.CurrentManagedThreadId;
- if (!_fIsReentrant)
- {
- if (id == _writeLockOwnerId)
- {
- //Check for AW->AR
- throw new LockRecursionException(SR.LockRecursionException_ReadAfterWriteNotAllowed);
- }
- _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
- lrwc = GetThreadRWCount(false);
- //Check if the reader lock is already acquired. Note, we could
- //check the presence of a reader by not allocating rwc (But that
- //would lead to two lookups in the common case. It's better to keep
- //a count in the structure).
- if (lrwc.readercount > 0)
- {
- _spinLock.Exit();
- throw new LockRecursionException(SR.LockRecursionException_RecursiveReadNotAllowed);
- }
- else if (id == _upgradeLockOwnerId)
- {
- //The upgrade lock is already held.
- //Update the global read counts and exit.
- lrwc.readercount++;
- _owners++;
- _spinLock.Exit();
- return true;
- }
- }
- else
- {
- _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
- lrwc = GetThreadRWCount(false);
- if (lrwc.readercount > 0)
- {
- lrwc.readercount++;
- _spinLock.Exit();
- return true;
- }
- else if (id == _upgradeLockOwnerId)
- {
- //The upgrade lock is already held.
- //Update the global read counts and exit.
- lrwc.readercount++;
- _owners++;
- _spinLock.Exit();
- _fUpgradeThreadHoldingRead = true;
- return true;
- }
- else if (id == _writeLockOwnerId)
- {
- //The write lock is already held.
- //Update global read counts here,
- lrwc.readercount++;
- _owners++;
- _spinLock.Exit();
- return true;
- }
- }
- bool retVal = true;
- int spinCount = 0;
- for (; ;)
- {
- // We can enter a read lock if there are only read-locks have been given out
- // and a writer is not trying to get in.
- if (_owners < MAX_READER)
- {
- // Good case, there is no contention, we are basically done
- _owners++; // Indicate we have another reader
- lrwc.readercount++;
- break;
- }
- if (timeout.IsExpired)
- {
- _spinLock.Exit();
- return false;
- }
- if (spinCount < MaxSpinCount && ShouldSpinForEnterAnyRead())
- {
- _spinLock.Exit();
- spinCount++;
- SpinWait(spinCount);
- _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
- //The per-thread structure may have been recycled as the lock is acquired (due to message pumping), load again.
- if (IsRwHashEntryChanged(lrwc))
- lrwc = GetThreadRWCount(false);
- continue;
- }
- // Drat, we need to wait. Mark that we have waiters and wait.
- if (_readEvent == null) // Create the needed event
- {
- LazyCreateEvent(ref _readEvent, EnterLockType.Read);
- if (IsRwHashEntryChanged(lrwc))
- lrwc = GetThreadRWCount(false);
- continue; // since we left the lock, start over.
- }
- retVal = WaitOnEvent(_readEvent, ref _numReadWaiters, timeout, EnterLockType.Read);
- if (!retVal)
- {
- return false;
- }
- if (IsRwHashEntryChanged(lrwc))
- lrwc = GetThreadRWCount(false);
- }
- _spinLock.Exit();
- return retVal;
- }
- public void EnterWriteLock()
- {
- TryEnterWriteLock(-1);
- }
- public bool TryEnterWriteLock(TimeSpan timeout)
- {
- return TryEnterWriteLock(new TimeoutTracker(timeout));
- }
- public bool TryEnterWriteLock(int millisecondsTimeout)
- {
- return TryEnterWriteLock(new TimeoutTracker(millisecondsTimeout));
- }
- private bool TryEnterWriteLock(TimeoutTracker timeout)
- {
- return TryEnterWriteLockCore(timeout);
- }
- private bool TryEnterWriteLockCore(TimeoutTracker timeout)
- {
- if (_fDisposed)
- throw new ObjectDisposedException(null);
- int id = Environment.CurrentManagedThreadId;
- ReaderWriterCount lrwc;
- bool upgradingToWrite = false;
- if (!_fIsReentrant)
- {
- EnterSpinLockReason enterMyLockReason;
- if (id == _writeLockOwnerId)
- {
- //Check for AW->AW
- throw new LockRecursionException(SR.LockRecursionException_RecursiveWriteNotAllowed);
- }
- else if (id == _upgradeLockOwnerId)
- {
- //AU->AW case is allowed once.
- upgradingToWrite = true;
- enterMyLockReason = EnterSpinLockReason.UpgradeToWrite;
- }
- else
- {
- enterMyLockReason = EnterSpinLockReason.EnterWrite;
- }
- _spinLock.Enter(enterMyLockReason);
- lrwc = GetThreadRWCount(true);
- //Can't acquire write lock with reader lock held.
- if (lrwc != null && lrwc.readercount > 0)
- {
- _spinLock.Exit();
- throw new LockRecursionException(SR.LockRecursionException_WriteAfterReadNotAllowed);
- }
- }
- else
- {
- EnterSpinLockReason enterMyLockReason;
- if (id == _writeLockOwnerId)
- {
- enterMyLockReason = EnterSpinLockReason.EnterRecursiveWrite;
- }
- else if (id == _upgradeLockOwnerId)
- {
- enterMyLockReason = EnterSpinLockReason.UpgradeToWrite;
- }
- else
- {
- enterMyLockReason = EnterSpinLockReason.EnterWrite;
- }
- _spinLock.Enter(enterMyLockReason);
- lrwc = GetThreadRWCount(false);
- if (id == _writeLockOwnerId)
- {
- lrwc.writercount++;
- _spinLock.Exit();
- return true;
- }
- else if (id == _upgradeLockOwnerId)
- {
- upgradingToWrite = true;
- }
- else if (lrwc.readercount > 0)
- {
- //Write locks may not be acquired if only read locks have been
- //acquired.
- _spinLock.Exit();
- throw new LockRecursionException(SR.LockRecursionException_WriteAfterReadNotAllowed);
- }
- }
- bool retVal = true;
- int spinCount = 0;
- for (; ;)
- {
- if (IsWriterAcquired())
- {
- // Good case, there is no contention, we are basically done
- SetWriterAcquired();
- break;
- }
- //Check if there is just one upgrader, and no readers.
- //Assumption: Only one thread can have the upgrade lock, so the
- //following check will fail for all other threads that may sneak in
- //when the upgrading thread is waiting.
- if (upgradingToWrite)
- {
- uint readercount = GetNumReaders();
- if (readercount == 1)
- {
- //Good case again, there is just one upgrader, and no readers.
- SetWriterAcquired(); // indicate we have a writer.
- break;
- }
- else if (readercount == 2)
- {
- if (lrwc != null)
- {
- if (IsRwHashEntryChanged(lrwc))
- lrwc = GetThreadRWCount(false);
- if (lrwc.readercount > 0)
- {
- //This check is needed for EU->ER->EW case, as the owner count will be two.
- Debug.Assert(_fIsReentrant);
- Debug.Assert(_fUpgradeThreadHoldingRead);
- //Good case again, there is just one upgrader, and no readers.
- SetWriterAcquired(); // indicate we have a writer.
- break;
- }
- }
- }
- }
- if (timeout.IsExpired)
- {
- _spinLock.Exit();
- return false;
- }
- if (spinCount < MaxSpinCount && ShouldSpinForEnterAnyWrite(upgradingToWrite))
- {
- _spinLock.Exit();
- spinCount++;
- SpinWait(spinCount);
- _spinLock.Enter(upgradingToWrite ? EnterSpinLockReason.UpgradeToWrite : EnterSpinLockReason.EnterWrite);
- continue;
- }
- if (upgradingToWrite)
- {
- if (_waitUpgradeEvent == null) // Create the needed event
- {
- LazyCreateEvent(ref _waitUpgradeEvent, EnterLockType.UpgradeToWrite);
- continue; // since we left the lock, start over.
- }
- Debug.Assert(_numWriteUpgradeWaiters == 0, "There can be at most one thread with the upgrade lock held.");
- retVal = WaitOnEvent(_waitUpgradeEvent, ref _numWriteUpgradeWaiters, timeout, EnterLockType.UpgradeToWrite);
- //The lock is not held in case of failure.
- if (!retVal)
- return false;
- }
- else
- {
- // Drat, we need to wait. Mark that we have waiters and wait.
- if (_writeEvent == null) // create the needed event.
- {
- LazyCreateEvent(ref _writeEvent, EnterLockType.Write);
- continue; // since we left the lock, start over.
- }
- retVal = WaitOnEvent(_writeEvent, ref _numWriteWaiters, timeout, EnterLockType.Write);
- //The lock is not held in case of failure.
- if (!retVal)
- return false;
- }
- }
- Debug.Assert((_owners & WRITER_HELD) > 0);
- if (_fIsReentrant)
- {
- if (IsRwHashEntryChanged(lrwc))
- lrwc = GetThreadRWCount(false);
- lrwc.writercount++;
- }
- _spinLock.Exit();
- _writeLockOwnerId = id;
- return true;
- }
- public void EnterUpgradeableReadLock()
- {
- TryEnterUpgradeableReadLock(-1);
- }
- public bool TryEnterUpgradeableReadLock(TimeSpan timeout)
- {
- return TryEnterUpgradeableReadLock(new TimeoutTracker(timeout));
- }
- public bool TryEnterUpgradeableReadLock(int millisecondsTimeout)
- {
- return TryEnterUpgradeableReadLock(new TimeoutTracker(millisecondsTimeout));
- }
- private bool TryEnterUpgradeableReadLock(TimeoutTracker timeout)
- {
- return TryEnterUpgradeableReadLockCore(timeout);
- }
- private bool TryEnterUpgradeableReadLockCore(TimeoutTracker timeout)
- {
- if (_fDisposed)
- throw new ObjectDisposedException(null);
- int id = Environment.CurrentManagedThreadId;
- ReaderWriterCount lrwc;
- if (!_fIsReentrant)
- {
- if (id == _upgradeLockOwnerId)
- {
- //Check for AU->AU
- throw new LockRecursionException(SR.LockRecursionException_RecursiveUpgradeNotAllowed);
- }
- else if (id == _writeLockOwnerId)
- {
- //Check for AU->AW
- throw new LockRecursionException(SR.LockRecursionException_UpgradeAfterWriteNotAllowed);
- }
- _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
- lrwc = GetThreadRWCount(true);
- //Can't acquire upgrade lock with reader lock held.
- if (lrwc != null && lrwc.readercount > 0)
- {
- _spinLock.Exit();
- throw new LockRecursionException(SR.LockRecursionException_UpgradeAfterReadNotAllowed);
- }
- }
- else
- {
- _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
- lrwc = GetThreadRWCount(false);
- if (id == _upgradeLockOwnerId)
- {
- lrwc.upgradecount++;
- _spinLock.Exit();
- return true;
- }
- else if (id == _writeLockOwnerId)
- {
- //Write lock is already held, Just update the global state
- //to show presence of upgrader.
- Debug.Assert((_owners & WRITER_HELD) > 0);
- _owners++;
- _upgradeLockOwnerId = id;
- lrwc.upgradecount++;
- if (lrwc.readercount > 0)
- _fUpgradeThreadHoldingRead = true;
- _spinLock.Exit();
- return true;
- }
- else if (lrwc.readercount > 0)
- {
- //Upgrade locks may not be acquired if only read locks have been
- //acquired.
- _spinLock.Exit();
- throw new LockRecursionException(SR.LockRecursionException_UpgradeAfterReadNotAllowed);
- }
- }
- bool retVal = true;
- int spinCount = 0;
- for (; ;)
- {
- //Once an upgrade lock is taken, it's like having a reader lock held
- //until upgrade or downgrade operations are performed.
- if ((_upgradeLockOwnerId == -1) && (_owners < MAX_READER))
- {
- _owners++;
- _upgradeLockOwnerId = id;
- break;
- }
- if (timeout.IsExpired)
- {
- _spinLock.Exit();
- return false;
- }
- if (spinCount < MaxSpinCount && ShouldSpinForEnterAnyRead())
- {
- _spinLock.Exit();
- spinCount++;
- SpinWait(spinCount);
- _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
- continue;
- }
- // Drat, we need to wait. Mark that we have waiters and wait.
- if (_upgradeEvent == null) // Create the needed event
- {
- LazyCreateEvent(ref _upgradeEvent, EnterLockType.UpgradeableRead);
- continue; // since we left the lock, start over.
- }
- //Only one thread with the upgrade lock held can proceed.
- retVal = WaitOnEvent(_upgradeEvent, ref _numUpgradeWaiters, timeout, EnterLockType.UpgradeableRead);
- if (!retVal)
- return false;
- }
- if (_fIsReentrant)
- {
- //The lock may have been dropped getting here, so make a quick check to see whether some other
- //thread did not grab the entry.
- if (IsRwHashEntryChanged(lrwc))
- lrwc = GetThreadRWCount(false);
- lrwc.upgradecount++;
- }
- _spinLock.Exit();
- return true;
- }
- public void ExitReadLock()
- {
- ReaderWriterCount lrwc = null;
- _spinLock.Enter(EnterSpinLockReason.ExitAnyRead);
- lrwc = GetThreadRWCount(true);
- if (lrwc == null || lrwc.readercount < 1)
- {
- //You have to be holding the read lock to make this call.
- _spinLock.Exit();
- throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedRead);
- }
- if (_fIsReentrant)
- {
- if (lrwc.readercount > 1)
- {
- lrwc.readercount--;
- _spinLock.Exit();
- return;
- }
- if (Environment.CurrentManagedThreadId == _upgradeLockOwnerId)
- {
- _fUpgradeThreadHoldingRead = false;
- }
- }
- Debug.Assert(_owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
- --_owners;
- Debug.Assert(lrwc.readercount == 1);
- lrwc.readercount--;
- ExitAndWakeUpAppropriateWaiters();
- }
- public void ExitWriteLock()
- {
- ReaderWriterCount lrwc;
- if (!_fIsReentrant)
- {
- if (Environment.CurrentManagedThreadId != _writeLockOwnerId)
- {
- //You have to be holding the write lock to make this call.
- throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedWrite);
- }
- _spinLock.Enter(EnterSpinLockReason.ExitAnyWrite);
- }
- else
- {
- _spinLock.Enter(EnterSpinLockReason.ExitAnyWrite);
- lrwc = GetThreadRWCount(false);
- if (lrwc == null)
- {
- _spinLock.Exit();
- throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedWrite);
- }
- if (lrwc.writercount < 1)
- {
- _spinLock.Exit();
- throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedWrite);
- }
- lrwc.writercount--;
- if (lrwc.writercount > 0)
- {
- _spinLock.Exit();
- return;
- }
- }
- Debug.Assert((_owners & WRITER_HELD) > 0, "Calling ReleaseWriterLock when no write lock is held");
- ClearWriterAcquired();
- _writeLockOwnerId = -1;
- ExitAndWakeUpAppropriateWaiters();
- }
- public void ExitUpgradeableReadLock()
- {
- ReaderWriterCount lrwc;
- if (!_fIsReentrant)
- {
- if (Environment.CurrentManagedThreadId != _upgradeLockOwnerId)
- {
- //You have to be holding the upgrade lock to make this call.
- throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedUpgrade);
- }
- _spinLock.Enter(EnterSpinLockReason.ExitAnyRead);
- }
- else
- {
- _spinLock.Enter(EnterSpinLockReason.ExitAnyRead);
- lrwc = GetThreadRWCount(true);
- if (lrwc == null)
- {
- _spinLock.Exit();
- throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedUpgrade);
- }
- if (lrwc.upgradecount < 1)
- {
- _spinLock.Exit();
- throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedUpgrade);
- }
- lrwc.upgradecount--;
- if (lrwc.upgradecount > 0)
- {
- _spinLock.Exit();
- return;
- }
- _fUpgradeThreadHoldingRead = false;
- }
- _owners--;
- _upgradeLockOwnerId = -1;
- ExitAndWakeUpAppropriateWaiters();
- }
- /// <summary>
- /// A routine for lazily creating a event outside the lock (so if errors
- /// happen they are outside the lock and that we don't do much work
- /// while holding a spin lock). If all goes well, reenter the lock and
- /// set 'waitEvent'
- /// </summary>
- private void LazyCreateEvent(ref EventWaitHandle waitEvent, EnterLockType enterLockType)
- {
- #if DEBUG
- Debug.Assert(_spinLock.IsHeld);
- Debug.Assert(waitEvent == null);
- #endif
- _spinLock.Exit();
- var newEvent =
- new EventWaitHandle(
- false,
- enterLockType == EnterLockType.Read ? EventResetMode.ManualReset : EventResetMode.AutoReset);
- EnterSpinLockReason enterMyLockReason;
- switch (enterLockType)
- {
- case EnterLockType.Read:
- case EnterLockType.UpgradeableRead:
- enterMyLockReason = EnterSpinLockReason.EnterAnyRead | EnterSpinLockReason.Wait;
- break;
- case EnterLockType.Write:
- enterMyLockReason = EnterSpinLockReason.EnterWrite | EnterSpinLockReason.Wait;
- break;
- default:
- Debug.Assert(enterLockType == EnterLockType.UpgradeToWrite);
- enterMyLockReason = EnterSpinLockReason.UpgradeToWrite | EnterSpinLockReason.Wait;
- break;
- }
- _spinLock.Enter(enterMyLockReason);
- if (waitEvent == null) // maybe someone snuck in.
- waitEvent = newEvent;
- else
- newEvent.Dispose();
- }
- /// <summary>
- /// Waits on 'waitEvent' with a timeout
- /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
- /// </summary>
- private bool WaitOnEvent(
- EventWaitHandle waitEvent,
- ref uint numWaiters,
- TimeoutTracker timeout,
- EnterLockType enterLockType)
- {
- #if DEBUG
- Debug.Assert(_spinLock.IsHeld);
- #endif
- WaiterStates waiterSignaledState = WaiterStates.None;
- EnterSpinLockReason enterMyLockReason;
- switch (enterLockType)
- {
- case EnterLockType.UpgradeableRead:
- waiterSignaledState = WaiterStates.UpgradeableReadWaiterSignaled;
- goto case EnterLockType.Read;
- case EnterLockType.Read:
- enterMyLockReason = EnterSpinLockReason.EnterAnyRead;
- break;
- case EnterLockType.Write:
- waiterSignaledState = WaiterStates.WriteWaiterSignaled;
- enterMyLockReason = EnterSpinLockReason.EnterWrite;
- break;
- default:
- Debug.Assert(enterLockType == EnterLockType.UpgradeToWrite);
- enterMyLockReason = EnterSpinLockReason.UpgradeToWrite;
- break;
- }
- // It was not possible to acquire the RW lock because some other thread was holding some type of lock. The other
- // thread, when it releases its lock, will wake appropriate waiters. Along with resetting the wait event, clear the
- // waiter signaled bit for this type of waiter if applicable, to indicate that a waiter of this type is no longer
- // signaled.
- //
- // If the waiter signaled bit is not updated upon event reset, the following scenario would lead to deadlock:
- // - Thread T0 signals the write waiter event or the upgradeable read waiter event to wake a waiter
- // - There are no threads waiting on the event, but T1 is in WaitOnEvent() after exiting the spin lock and before
- // actually waiting on the event (that is, it's recorded that there is one waiter for the event). It remains in
- // this region for a while, in the repro case it typically gets context-switched out.
- // - T2 acquires the RW lock in some fashion that blocks T0 or T3 from acquiring the RW lock
- // - T0 or T3 fails to acquire the RW lock enough times for it to enter WaitOnEvent for the same event as T1
- // - T0 or T3 resets the event
- // - T2 releases the RW lock and does not wake a waiter because the reset at the previous step lost a signal but
- // _waiterStates was not updated to reflect that
- // - T1 and other threads begin waiting on the event, but there's no longer any thread that would wake them
- if (waiterSignaledState != WaiterStates.None && (_waiterStates & waiterSignaledState) != WaiterStates.None)
- {
- _waiterStates &= ~waiterSignaledState;
- }
- waitEvent.Reset();
- numWaiters++;
- HasNoWaiters = false;
- //Setting these bits will prevent new readers from getting in.
- if (_numWriteWaiters == 1)
- SetWritersWaiting();
- if (_numWriteUpgradeWaiters == 1)
- SetUpgraderWaiting();
- bool waitSuccessful = false;
- _spinLock.Exit(); // Do the wait outside of any lock
- try
- {
- waitSuccessful = waitEvent.WaitOne(timeout.RemainingMilliseconds);
- }
- finally
- {
- _spinLock.Enter(enterMyLockReason);
- --numWaiters;
- if (waitSuccessful &&
- waiterSignaledState != WaiterStates.None &&
- (_waiterStates & waiterSignaledState) != WaiterStates.None)
- {
- // Indicate that a signaled waiter of this type has woken. Since non-read waiters are signaled to wake one
- // at a time, we avoid waking up more than one waiter of that type upon successive enter/exit loops until
- // the signaled thread actually wakes up. For example, if there are multiple write waiters and one thread is
- // repeatedly entering and exiting a write lock, every exit would otherwise signal a different write waiter
- // to wake up unnecessarily when only one woken waiter may actually succeed in entering the write lock.
- _waiterStates &= ~waiterSignaledState;
- }
- if (_numWriteWaiters == 0 && _numWriteUpgradeWaiters == 0 && _numUpgradeWaiters == 0 && _numReadWaiters == 0)
- HasNoWaiters = true;
- if (_numWriteWaiters == 0)
- ClearWritersWaiting();
- if (_numWriteUpgradeWaiters == 0)
- ClearUpgraderWaiting();
- if (!waitSuccessful) // We may also be about to throw for some reason. Exit myLock.
- {
- if (enterLockType >= EnterLockType.Write)
- {
- // Write waiters block read waiters from acquiring the lock. Since this was the last write waiter, try
- // to wake up the appropriate read waiters.
- ExitAndWakeUpAppropriateReadWaiters();
- }
- else
- {
- _spinLock.Exit();
- }
- }
- }
- return waitSuccessful;
- }
- /// <summary>
- /// Determines the appropriate events to set, leaves the locks, and sets the events.
- /// </summary>
- private void ExitAndWakeUpAppropriateWaiters()
- {
- #if DEBUG
- Debug.Assert(_spinLock.IsHeld);
- #endif
- if (HasNoWaiters)
- {
- _spinLock.Exit();
- return;
- }
- ExitAndWakeUpAppropriateWaitersPreferringWriters();
- }
- private void ExitAndWakeUpAppropriateWaitersPreferringWriters()
- {
- uint readercount = GetNumReaders();
- //We need this case for EU->ER->EW case, as the read count will be 2 in
- //that scenario.
- if (_fIsReentrant)
- {
- if (_numWriteUpgradeWaiters > 0 && _fUpgradeThreadHoldingRead && readercount == 2)
- {
- _spinLock.Exit(); // Exit before signaling to improve efficiency (wakee will need the lock)
- _waitUpgradeEvent.Set(); // release all upgraders (however there can be at most one).
- return;
- }
- }
- if (readercount == 1 && _numWriteUpgradeWaiters > 0)
- {
- //We have to be careful now, as we are dropping the lock.
- //No new writes should be allowed to sneak in if an upgrade
- //was pending.
- _spinLock.Exit(); // Exit before signaling to improve efficiency (wakee will need the lock)
- _waitUpgradeEvent.Set(); // release all upgraders (however there can be at most one).
- }
- else if (readercount == 0 && _numWriteWaiters > 0)
- {
- // Check if a waiter of the same type has already been signaled but hasn't woken yet. If so, avoid signaling
- // and waking another waiter unnecessarily.
- WaiterStates signaled = _waiterStates & WaiterStates.WriteWaiterSignaled;
- if (signaled == WaiterStates.None)
- {
- _waiterStates |= WaiterStates.WriteWaiterSignaled;
- }
- _spinLock.Exit(); // Exit before signaling to improve efficiency (wakee will need the lock)
- if (signaled == WaiterStates.None)
- {
- _writeEvent.Set(); // release one writer.
- }
- }
- else
- {
- ExitAndWakeUpAppropriateReadWaiters();
- }
- }
- private void ExitAndWakeUpAppropriateReadWaiters()
- {
- #if DEBUG
- Debug.Assert(_spinLock.IsHeld);
- #endif
- if (_numWriteWaiters != 0 || _numWriteUpgradeWaiters != 0 || HasNoWaiters)
- {
- _spinLock.Exit();
- return;
- }
- Debug.Assert(_numReadWaiters != 0 || _numUpgradeWaiters != 0);
- bool setReadEvent = _numReadWaiters != 0;
- bool setUpgradeEvent = _numUpgradeWaiters != 0 && _upgradeLockOwnerId == -1;
- if (setUpgradeEvent)
- {
- // Check if a waiter of the same type has already been signaled but hasn't woken yet. If so, avoid signaling
- // and waking another waiter unnecessarily.
- if ((_waiterStates & WaiterStates.UpgradeableReadWaiterSignaled) == WaiterStates.None)
- {
- _waiterStates |= WaiterStates.UpgradeableReadWaiterSignaled;
- }
- else
- {
- setUpgradeEvent = false;
- }
- }
- _spinLock.Exit(); // Exit before signaling to improve efficiency (wakee will need the lock)
- if (setReadEvent)
- _readEvent.Set(); // release all readers.
- if (setUpgradeEvent)
- _upgradeEvent.Set(); //release one upgrader.
- }
- private bool IsWriterAcquired()
- {
- return (_owners & ~WAITING_WRITERS) == 0;
- }
- private void SetWriterAcquired()
- {
- _owners |= WRITER_HELD; // indicate we have a writer.
- }
- private void ClearWriterAcquired()
- {
- _owners &= ~WRITER_HELD;
- }
- private void SetWritersWaiting()
- {
- _owners |= WAITING_WRITERS;
- }
- private void ClearWritersWaiting()
- {
- _owners &= ~WAITING_WRITERS;
- }
- private void SetUpgraderWaiting()
- {
- _owners |= WAITING_UPGRADER;
- }
- private void ClearUpgraderWaiting()
- {
- _owners &= ~WAITING_UPGRADER;
- }
- private uint GetNumReaders()
- {
- return _owners & READER_MASK;
- }
- private bool ShouldSpinForEnterAnyRead()
- {
- // If there is a write waiter or write upgrade waiter, the waiter would block a reader from acquiring the RW lock
- // because the waiter takes precedence. In that case, the reader is not likely to make progress by spinning.
- // Although another thread holding a write lock would prevent this thread from acquiring a read lock, it is by
- // itself not a good enough reason to skip spinning.
- return HasNoWaiters || (_numWriteWaiters == 0 && _numWriteUpgradeWaiters == 0);
- }
- private bool ShouldSpinForEnterAnyWrite(bool isUpgradeToWrite)
- {
- // If there is a write upgrade waiter, the waiter would block a writer from acquiring the RW lock because the waiter
- // holds a read lock. In that case, the writer is not likely to make progress by spinning. Regarding upgrading to a
- // write lock, there is no type of waiter that would block the upgrade from happening. Although another thread
- // holding a read or write lock would prevent this thread from acquiring the write lock, it is by itself not a good
- // enough reason to skip spinning.
- return isUpgradeToWrite || _numWriteUpgradeWaiters == 0;
- }
- private static void SpinWait(int spinCount)
- {
- const int LockSpinCycles = 20;
- //Exponential back-off
- if ((spinCount < 5) && (ProcessorCount > 1))
- {
- RuntimeThread.SpinWait(LockSpinCycles * spinCount);
- }
- else
- {
- RuntimeThread.Sleep(0);
- }
- // Don't want to Sleep(1) in this spin wait:
- // - Don't want to spin for that long, since a proper wait will follow when the spin wait fails. The artificial
- // delay introduced by Sleep(1) will in some cases be much longer than desired.
- // - Sleep(1) would put the thread into a wait state, and a proper wait will follow when the spin wait fails
- // anyway, so it's preferable to put the thread into the proper wait state
- }
- public void Dispose()
- {
- Dispose(true);
- }
- private void Dispose(bool disposing)
- {
- if (disposing && !_fDisposed)
- {
- if (WaitingReadCount > 0 || WaitingUpgradeCount > 0 || WaitingWriteCount > 0)
- throw new SynchronizationLockException(SR.SynchronizationLockException_IncorrectDispose);
- if (IsReadLockHeld || IsUpgradeableReadLockHeld || IsWriteLockHeld)
- throw new SynchronizationLockException(SR.SynchronizationLockException_IncorrectDispose);
- if (_writeEvent != null)
- {
- _writeEvent.Dispose();
- _writeEvent = null;
- }
- if (_readEvent != null)
- {
- _readEvent.Dispose();
- _readEvent = null;
- }
- if (_upgradeEvent != null)
- {
- _upgradeEvent.Dispose();
- _upgradeEvent = null;
- }
- if (_waitUpgradeEvent != null)
- {
- _waitUpgradeEvent.Dispose();
- _waitUpgradeEvent = null;
- }
- _fDisposed = true;
- }
- }
- public bool IsReadLockHeld
- {
- get
- {
- if (RecursiveReadCount > 0)
- return true;
- else
- return false;
- }
- }
- public bool IsUpgradeableReadLockHeld
- {
- get
- {
- if (RecursiveUpgradeCount > 0)
- return true;
- else
- return false;
- }
- }
- public bool IsWriteLockHeld
- {
- get
- {
- if (RecursiveWriteCount > 0)
- return true;
- else
- return false;
- }
- }
- public LockRecursionPolicy RecursionPolicy
- {
- get
- {
- if (_fIsReentrant)
- {
- return LockRecursionPolicy.SupportsRecursion;
- }
- else
- {
- return LockRecursionPolicy.NoRecursion;
- }
- }
- }
- public int CurrentReadCount
- {
- get
- {
- int numreaders = (int)GetNumReaders();
- if (_upgradeLockOwnerId != -1)
- return numreaders - 1;
- else
- return numreaders;
- }
- }
- public int RecursiveReadCount
- {
- get
- {
- int count = 0;
- ReaderWriterCount lrwc = GetThreadRWCount(true);
- if (lrwc != null)
- count = lrwc.readercount;
- return count;
- }
- }
- public int RecursiveUpgradeCount
- {
- get
- {
- if (_fIsReentrant)
- {
- int count = 0;
- ReaderWriterCount lrwc = GetThreadRWCount(true);
- if (lrwc != null)
- count = lrwc.upgradecount;
- return count;
- }
- else
- {
- if (Environment.CurrentManagedThreadId == _upgradeLockOwnerId)
- return 1;
- else
- return 0;
- }
- }
- }
- public int RecursiveWriteCount
- {
- get
- {
- if (_fIsReentrant)
- {
- int count = 0;
- ReaderWriterCount lrwc = GetThreadRWCount(true);
- if (lrwc != null)
- count = lrwc.writercount;
- return count;
- }
- else
- {
- if (Environment.CurrentManagedThreadId == _writeLockOwnerId)
- return 1;
- else
- return 0;
- }
- }
- }
- public int WaitingReadCount
- {
- get
- {
- return (int)_numReadWaiters;
- }
- }
- public int WaitingUpgradeCount
- {
- get
- {
- return (int)_numUpgradeWaiters;
- }
- }
- public int WaitingWriteCount
- {
- get
- {
- return (int)_numWriteWaiters;
- }
- }
- private struct SpinLock
- {
- private int _isLocked;
- /// <summary>
- /// Used to deprioritize threads attempting to enter the lock when they would not make progress after doing so.
- /// <see cref="EnterSpin(EnterSpinLockReason)"/> avoids acquiring the lock as long as the operation for which it
- /// was called is deprioritized.
- ///
- /// Layout:
- /// - Low 16 bits: Number of threads that have deprioritized an enter-any-write operation
- /// - High 16 bits: Number of threads that have deprioritized an enter-any-read operation
- /// </summary>
- private int _enterDeprioritizationState;
- // Layout-specific constants for _enterDeprioritizationState
- private const int DeprioritizeEnterAnyReadIncrement = 1 << 16;
- private const int DeprioritizeEnterAnyWriteIncrement = 1;
- // The variables controlling spinning behavior of this spin lock
- private const int LockSpinCycles = 20;
- private const int LockSpinCount = 10;
- private const int LockSleep0Count = 5;
- private const int DeprioritizedLockSleep1Count = 5;
- private static int GetEnterDeprioritizationStateChange(EnterSpinLockReason reason)
- {
- EnterSpinLockReason operation = reason & EnterSpinLockReason.OperationMask;
- switch (operation)
- {
- case EnterSpinLockReason.EnterAnyRead:
- return 0;
- case EnterSpinLockReason.ExitAnyRead:
- // A read lock is held until this thread is able to exit it, so deprioritize enter-write threads as they
- // will not be able to make progress
- return DeprioritizeEnterAnyWriteIncrement;
- case EnterSpinLockReason.EnterWrite:
- // Writers are typically much less frequent and much less in number than readers. Waiting writers take
- // precedence over new read attempts in order to let current readers release their lock and allow a
- // writer to obtain the lock. Before a writer can register as a waiter though, the presence of just
- // relatively few enter-read spins can easily starve the enter-write from even entering this lock,
- // delaying its spin loop for an unreasonable duration.
- //
- // Deprioritize enter-read to preference enter-write. This makes it easier for enter-write threads to
- // starve enter-read threads. However, writers can already by design starve readers. A waiting writer
- // blocks enter-read threads and a new enter-write that needs to wait will be given precedence over
- // previously waiting enter-read threads. So this is not a new problem, and the RW lock is designed for
- // scenarios where writers are rare compared to readers.
- return DeprioritizeEnterAnyReadIncrement;
- default:
- Debug.Assert(
- operation == EnterSpinLockReason.UpgradeToWrite ||
- operation == EnterSpinLockReason.EnterRecursiveWrite ||
- operation == EnterSpinLockReason.ExitAnyWrite);
- // UpgradeToWrite:
- // - A read lock is held and an exit-read is not nearby, so deprioritize enter-write threads as they
- // will not be able to make progress. This thread also intends to enter a write lock, so deprioritize
- // enter -read threads as well, see case EnterSpinLockReason.EnterWrite for the rationale.
- // EnterRecursiveWrite, ExitAnyWrite:
- // - In both cases, a write lock is held until this thread is able to exit it, so deprioritize
- // enter -read and enter-write threads as they will not be able to make progress
- return DeprioritizeEnterAnyReadIncrement + DeprioritizeEnterAnyWriteIncrement;
- }
- }
- private ushort EnterForEnterAnyReadDeprioritizedCount
- {
- get
- {
- Debug.Assert(DeprioritizeEnterAnyReadIncrement == (1 << 16));
- return (ushort)((uint)_enterDeprioritizationState >> 16);
- }
- }
- private ushort EnterForEnterAnyWriteDeprioritizedCount
- {
- get
- {
- Debug.Assert(DeprioritizeEnterAnyWriteIncrement == 1);
- return (ushort)_enterDeprioritizationState;
- }
- }
- private bool IsEnterDeprioritized(EnterSpinLockReason reason)
- {
- Debug.Assert((reason & EnterSpinLockReason.Wait) != 0 || reason == (reason & EnterSpinLockReason.OperationMask));
- Debug.Assert(
- (reason & EnterSpinLockReason.Wait) == 0 ||
- (reason & EnterSpinLockReason.OperationMask) == EnterSpinLockReason.EnterAnyRead ||
- (reason & EnterSpinLockReason.OperationMask) == EnterSpinLockReason.EnterWrite ||
- (reason & EnterSpinLockReason.OperationMask) == EnterSpinLockReason.UpgradeToWrite);
- switch (reason)
- {
- default:
- Debug.Assert(
- (reason & EnterSpinLockReason.Wait) != 0 ||
- reason == EnterSpinLockReason.ExitAnyRead ||
- reason == EnterSpinLockReason.EnterRecursiveWrite ||
- reason == EnterSpinLockReason.ExitAnyWrite);
- return false;
- case EnterSpinLockReason.EnterAnyRead:
- return EnterForEnterAnyReadDeprioritizedCount != 0;
- case EnterSpinLockReason.EnterWrite:
- Debug.Assert((GetEnterDeprioritizationStateChange(reason) & DeprioritizeEnterAnyWriteIncrement) == 0);
- return EnterForEnterAnyWriteDeprioritizedCount != 0;
- case EnterSpinLockReason.UpgradeToWrite:
- Debug.Assert((GetEnterDeprioritizationStateChange(reason) & DeprioritizeEnterAnyWriteIncrement) != 0);
- return EnterForEnterAnyWriteDeprioritizedCount > 1;
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool TryEnter()
- {
- return Interlocked.CompareExchange(ref _isLocked, 1, 0) == 0;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Enter(EnterSpinLockReason reason)
- {
- if (!TryEnter())
- {
- EnterSpin(reason);
- }
- }
- private void EnterSpin(EnterSpinLockReason reason)
- {
- int deprioritizationStateChange = GetEnterDeprioritizationStateChange(reason);
- if (deprioritizationStateChange != 0)
- {
- Interlocked.Add(ref _enterDeprioritizationState, deprioritizationStateChange);
- }
- int processorCount = ProcessorCount;
- for (int spinIndex = 0; ; spinIndex++)
- {
- if (spinIndex < LockSpinCount && processorCount > 1)
- {
- RuntimeThread.SpinWait(LockSpinCycles * (spinIndex + 1)); // Wait a few dozen instructions to let another processor release lock.
- }
- else if (spinIndex < (LockSpinCount + LockSleep0Count))
- {
- RuntimeThread.Sleep(0); // Give up my quantum.
- }
- else
- {
- RuntimeThread.Sleep(1); // Give up my quantum.
- }
- if (!IsEnterDeprioritized(reason))
- {
- if (_isLocked == 0 && TryEnter())
- {
- if (deprioritizationStateChange != 0)
- {
- Interlocked.Add(ref _enterDeprioritizationState, -deprioritizationStateChange);
- }
- return;
- }
- continue;
- }
- // It's possible for an Enter thread to be deprioritized for an extended duration. It's undesirable for a
- // deprioritized thread to keep waking up to spin despite a Sleep(1) when a large number of such threads are
- // involved. After a threshold of Sleep(1)s, ignore the deprioritization and enter this lock to allow this
- // thread to stop spinning and hopefully enter a proper wait state.
- Debug.Assert(
- reason == EnterSpinLockReason.EnterAnyRead ||
- reason == EnterSpinLockReason.EnterWrite ||
- reason == EnterSpinLockReason.UpgradeToWrite);
- if (spinIndex >= (LockSpinCount + LockSleep0Count + DeprioritizedLockSleep1Count))
- {
- reason |= EnterSpinLockReason.Wait;
- spinIndex = -1;
- }
- }
- }
- public void Exit()
- {
- Debug.Assert(_isLocked != 0, "Exiting spin lock that is not held");
- Volatile.Write(ref _isLocked, 0);
- }
- #if DEBUG
- public bool IsHeld => _isLocked != 0;
- #endif
- }
- [Flags]
- private enum WaiterStates : byte
- {
- None = 0x0,
- // Used for quick check when there are no waiters
- NoWaiters = 0x1,
- // Used to avoid signaling more than one waiter to wake up when only one can make progress, see WaitOnEvent
- WriteWaiterSignaled = 0x2,
- UpgradeableReadWaiterSignaled = 0x4
- // Write upgrade waiters are excluded because there can only be one at any given time
- }
- private enum EnterSpinLockReason
- {
- EnterAnyRead = 0,
- ExitAnyRead = 1,
- EnterWrite = 2,
- UpgradeToWrite = 3,
- EnterRecursiveWrite = 4,
- ExitAnyWrite = 5,
- OperationMask = 0x7,
- Wait = 0x8
- }
- private enum EnterLockType
- {
- Read,
- UpgradeableRead,
- Write,
- UpgradeToWrite
- }
- }
- }
|