From: Jan Kotas Date: Tue, 11 Apr 2017 20:22:50 +0000 (-0700) Subject: Move a few more types from CoreFX to CoreLib (dotnet/coreclr#10893) X-Git-Tag: submit/tizen/20210909.063632~11030^2~7311 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=5762dbbb121d23894f5beb9e69fdd85883642e01;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Move a few more types from CoreFX to CoreLib (dotnet/coreclr#10893) Reducing shards between CoreCLR and CoreRT shards Commit migrated from https://github.com/dotnet/coreclr/commit/0d06efe81389e05df7b3bdd56c874720a13dd8b2 --- diff --git a/src/coreclr/src/mscorlib/Resources/Strings.resx b/src/coreclr/src/mscorlib/Resources/Strings.resx index 0ff4814..a0f814e 100644 --- a/src/coreclr/src/mscorlib/Resources/Strings.resx +++ b/src/coreclr/src/mscorlib/Resources/Strings.resx @@ -3566,4 +3566,46 @@ ---- Assert Short Message ---- + + A read lock may not be acquired with the write lock held in this mode. + + + Recursive read lock acquisitions not allowed in this mode. + + + Recursive write lock acquisitions not allowed in this mode. + + + A read lock may not be acquired with the write lock held in this mode. + + + Recursive upgradeable lock acquisitions not allowed in this mode. + + + Recursive read lock acquisitions not allowed in this mode. + + + Write lock may not be acquired with read lock held. This pattern is prone to deadlocks. Please ensure that read locks are released before taking a write lock. If an upgrade is necessary, use an upgrade lock in place of the read lock. + + + Write lock may not be acquired with read lock held. This pattern is prone to deadlocks. Please ensure that read locks are released before taking a write lock. If an upgrade is necessary, use an upgrade lock in place of the read lock. + + + The upgradeable lock is being released without being held. + + + The read lock is being released without being held. + + + The lock is being disposed while still being used. It either is being held by a thread and/or has active waiters waiting to acquire the lock. + + + Upgradeable lock may not be acquired with read lock held. + + + Upgradeable lock may not be acquired with write lock held in this mode. Acquiring Upgradeable lock gives the ability to read along with an option to upgrade to a writer. + + + The write lock is being released without being held. + diff --git a/src/coreclr/src/mscorlib/System.Private.CoreLib.csproj b/src/coreclr/src/mscorlib/System.Private.CoreLib.csproj index 76027f8..14f0c7b 100644 --- a/src/coreclr/src/mscorlib/System.Private.CoreLib.csproj +++ b/src/coreclr/src/mscorlib/System.Private.CoreLib.csproj @@ -528,6 +528,7 @@ + diff --git a/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems index 01ad69f..a481ef4 100644 --- a/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems @@ -57,6 +57,7 @@ + @@ -274,6 +275,7 @@ + @@ -284,8 +286,10 @@ + + diff --git a/src/coreclr/src/mscorlib/shared/System/ComponentModel/DefaultValueAttribute.cs b/src/coreclr/src/mscorlib/shared/System/ComponentModel/DefaultValueAttribute.cs new file mode 100644 index 0000000..3cdc907 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/ComponentModel/DefaultValueAttribute.cs @@ -0,0 +1,228 @@ +// 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 System; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace System.ComponentModel +{ + /// + /// Specifies the default value for a property. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes")] + [AttributeUsage(AttributeTargets.All)] + public class DefaultValueAttribute : Attribute + { + /// + /// This is the default value. + /// + private object _value; + + /// + /// Initializes a new instance of the class, converting the + /// specified value to the + /// specified type, and using the U.S. English culture as the + /// translation + /// context. + /// + public DefaultValueAttribute(Type type, string value) + { + // The try/catch here is because attributes should never throw exceptions. We would fail to + // load an otherwise normal class. + try + { + if (type.IsSubclassOf(typeof(Enum))) + { + _value = Enum.Parse(type, value, true); + } + else if (type == typeof(TimeSpan)) + { + _value = TimeSpan.Parse(value); + } + else + { + _value = Convert.ChangeType(value, type, CultureInfo.InvariantCulture); + } + } + catch + { + } + } + + /// + /// Initializes a new instance of the class using a Unicode + /// character. + /// + public DefaultValueAttribute(char value) + { + _value = value; + } + /// + /// Initializes a new instance of the class using an 8-bit unsigned + /// integer. + /// + public DefaultValueAttribute(byte value) + { + _value = value; + } + /// + /// Initializes a new instance of the class using a 16-bit signed + /// integer. + /// + public DefaultValueAttribute(short value) + { + _value = value; + } + /// + /// Initializes a new instance of the class using a 32-bit signed + /// integer. + /// + public DefaultValueAttribute(int value) + { + _value = value; + } + /// + /// Initializes a new instance of the class using a 64-bit signed + /// integer. + /// + public DefaultValueAttribute(long value) + { + _value = value; + } + /// + /// Initializes a new instance of the class using a + /// single-precision floating point + /// number. + /// + public DefaultValueAttribute(float value) + { + _value = value; + } + /// + /// Initializes a new instance of the class using a + /// double-precision floating point + /// number. + /// + public DefaultValueAttribute(double value) + { + _value = value; + } + /// + /// Initializes a new instance of the class using a + /// value. + /// + public DefaultValueAttribute(bool value) + { + _value = value; + } + /// + /// Initializes a new instance of the class using a . + /// + public DefaultValueAttribute(string value) + { + _value = value; + } + + /// + /// Initializes a new instance of the + /// class. + /// + public DefaultValueAttribute(object value) + { + _value = value; + } + + /// + /// Initializes a new instance of the class using a + /// value. + /// + [CLSCompliant(false)] + public DefaultValueAttribute(sbyte value) + { + _value = value; + } + + /// + /// Initializes a new instance of the class using a + /// value. + /// + [CLSCompliant(false)] + public DefaultValueAttribute(ushort value) + { + _value = value; + } + + /// + /// Initializes a new instance of the class using a + /// value. + /// + [CLSCompliant(false)] + public DefaultValueAttribute(uint value) + { + _value = value; + } + + /// + /// Initializes a new instance of the class using a + /// value. + /// + [CLSCompliant(false)] + public DefaultValueAttribute(ulong value) + { + _value = value; + } + + /// + /// + /// Gets the default value of the property this + /// attribute is + /// bound to. + /// + /// + public virtual object Value + { + get + { + return _value; + } + } + + public override bool Equals(object obj) + { + if (obj == this) + { + return true; + } + + DefaultValueAttribute other = obj as DefaultValueAttribute; + + if (other != null) + { + if (Value != null) + { + return Value.Equals(other.Value); + } + else + { + return (other.Value == null); + } + } + return false; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + protected void SetValue(object value) + { + _value = value; + } + } +} diff --git a/src/coreclr/src/mscorlib/shared/System/Runtime/CompilerServices/IsConst.cs b/src/coreclr/src/mscorlib/shared/System/Runtime/CompilerServices/IsConst.cs new file mode 100644 index 0000000..7f948b6 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Runtime/CompilerServices/IsConst.cs @@ -0,0 +1,10 @@ +// 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. + +namespace System.Runtime.CompilerServices +{ + public static partial class IsConst + { + } +} diff --git a/src/coreclr/src/mscorlib/shared/System/Runtime/CompilerServices/SpecialNameAttribute.cs b/src/coreclr/src/mscorlib/shared/System/Runtime/CompilerServices/SpecialNameAttribute.cs new file mode 100644 index 0000000..b18e628 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Runtime/CompilerServices/SpecialNameAttribute.cs @@ -0,0 +1,12 @@ +// 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. + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Struct)] + public sealed class SpecialNameAttribute : Attribute + { + public SpecialNameAttribute() { } + } +} diff --git a/src/coreclr/src/mscorlib/shared/System/Runtime/CompilerServices/StrongBox.cs b/src/coreclr/src/mscorlib/shared/System/Runtime/CompilerServices/StrongBox.cs new file mode 100644 index 0000000..0a1a565 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Runtime/CompilerServices/StrongBox.cs @@ -0,0 +1,59 @@ +// 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. + +namespace System.Runtime.CompilerServices +{ + /// + /// Holds a reference to a value. + /// + /// The type of the value that the references. + public class StrongBox : IStrongBox + { + /// + /// Gets the strongly typed value associated with the + /// This is explicitly exposed as a field instead of a property to enable loading the address of the field. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] + public T Value; + + /// + /// Initializes a new StrongBox which can receive a value when used in a reference call. + /// + public StrongBox() + { + } + + /// + /// Initializes a new with the specified value. + /// + /// A value that the will reference. + public StrongBox(T value) + { + Value = value; + } + + object IStrongBox.Value + { + get + { + return Value; + } + set + { + Value = (T)value; + } + } + } + + /// + /// Defines a property for accessing the value that an object references. + /// + public interface IStrongBox + { + /// + /// Gets or sets the value the object references. + /// + object Value { get; set; } + } +} diff --git a/src/coreclr/src/mscorlib/src/System/Threading/ReaderWriterLockSlim.cs b/src/coreclr/src/mscorlib/src/System/Threading/ReaderWriterLockSlim.cs new file mode 100644 index 0000000..98517ad --- /dev/null +++ b/src/coreclr/src/mscorlib/src/System/Threading/ReaderWriterLockSlim.cs @@ -0,0 +1,1311 @@ +// 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.Threading; +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; + } + + /// + /// 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). + /// + public class ReaderWriterLockSlim : IDisposable + { + //Specifying if locked can be reacquired recursively. + private bool _fIsReentrant; + + // Lock specification for myLock: 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). + private int _myLock; + + //The variables controlling spinning behavior of Mylock(which is a spin-lock) + + private const int LockSpinCycles = 20; + private const int LockSpinCount = 10; + private const int LockSleep0Count = 5; + + // 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; + + //Variable used for quick check when there are no waiters. + private bool _fNoWaiters; + + 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(); + _fNoWaiters = true; + _lockID = Interlocked.Increment(ref s_nextLockID); + } + + [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; + } + + /// + /// 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. + /// + [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)Int32.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); + } + + EnterMyLock(); + + 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) + { + ExitMyLock(); + 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++; + ExitMyLock(); + return true; + } + } + else + { + EnterMyLock(); + lrwc = GetThreadRWCount(false); + if (lrwc.readercount > 0) + { + lrwc.readercount++; + ExitMyLock(); + return true; + } + else if (id == _upgradeLockOwnerId) + { + //The upgrade lock is already held. + //Update the global read counts and exit. + lrwc.readercount++; + _owners++; + ExitMyLock(); + _fUpgradeThreadHoldingRead = true; + return true; + } + else if (id == _writeLockOwnerId) + { + //The write lock is already held. + //Update global read counts here, + lrwc.readercount++; + _owners++; + ExitMyLock(); + 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 (spincount < MaxSpinCount) + { + ExitMyLock(); + if (timeout.IsExpired) + return false; + spincount++; + SpinWait(spincount); + EnterMyLock(); + //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, false); + if (IsRwHashEntryChanged(lrwc)) + lrwc = GetThreadRWCount(false); + continue; // since we left the lock, start over. + } + + retVal = WaitOnEvent(_readEvent, ref _numReadWaiters, timeout, isWriteWaiter: false); + if (!retVal) + { + return false; + } + if (IsRwHashEntryChanged(lrwc)) + lrwc = GetThreadRWCount(false); + } + + ExitMyLock(); + 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) + { + 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; + } + + EnterMyLock(); + lrwc = GetThreadRWCount(true); + + //Can't acquire write lock with reader lock held. + if (lrwc != null && lrwc.readercount > 0) + { + ExitMyLock(); + throw new LockRecursionException(SR.LockRecursionException_WriteAfterReadNotAllowed); + } + } + else + { + EnterMyLock(); + lrwc = GetThreadRWCount(false); + + if (id == _writeLockOwnerId) + { + lrwc.writercount++; + ExitMyLock(); + 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. + ExitMyLock(); + throw new LockRecursionException(SR.LockRecursionException_WriteAfterReadNotAllowed); + } + } + + int spincount = 0; + bool retVal = true; + + 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 (spincount < MaxSpinCount) + { + ExitMyLock(); + if (timeout.IsExpired) + return false; + spincount++; + SpinWait(spincount); + EnterMyLock(); + continue; + } + + if (upgradingToWrite) + { + if (_waitUpgradeEvent == null) // Create the needed event + { + LazyCreateEvent(ref _waitUpgradeEvent, true); + 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, isWriteWaiter: true); + + //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, true); + continue; // since we left the lock, start over. + } + + retVal = WaitOnEvent(_writeEvent, ref _numWriteWaiters, timeout, isWriteWaiter: true); + //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++; + } + + ExitMyLock(); + + _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); + } + + EnterMyLock(); + lrwc = GetThreadRWCount(true); + //Can't acquire upgrade lock with reader lock held. + if (lrwc != null && lrwc.readercount > 0) + { + ExitMyLock(); + throw new LockRecursionException(SR.LockRecursionException_UpgradeAfterReadNotAllowed); + } + } + else + { + EnterMyLock(); + lrwc = GetThreadRWCount(false); + + if (id == _upgradeLockOwnerId) + { + lrwc.upgradecount++; + ExitMyLock(); + 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; + ExitMyLock(); + return true; + } + else if (lrwc.readercount > 0) + { + //Upgrade locks may not be acquired if only read locks have been + //acquired. + ExitMyLock(); + 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 (spincount < MaxSpinCount) + { + ExitMyLock(); + if (timeout.IsExpired) + return false; + spincount++; + SpinWait(spincount); + EnterMyLock(); + continue; + } + + // Drat, we need to wait. Mark that we have waiters and wait. + if (_upgradeEvent == null) // Create the needed event + { + LazyCreateEvent(ref _upgradeEvent, true); + continue; // since we left the lock, start over. + } + + //Only one thread with the upgrade lock held can proceed. + retVal = WaitOnEvent(_upgradeEvent, ref _numUpgradeWaiters, timeout, isWriteWaiter: false); + 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++; + } + + ExitMyLock(); + + return true; + } + + public void ExitReadLock() + { + ReaderWriterCount lrwc = null; + + EnterMyLock(); + + lrwc = GetThreadRWCount(true); + + if (lrwc == null || lrwc.readercount < 1) + { + //You have to be holding the read lock to make this call. + ExitMyLock(); + throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedRead); + } + + if (_fIsReentrant) + { + if (lrwc.readercount > 1) + { + lrwc.readercount--; + ExitMyLock(); + 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); + } + EnterMyLock(); + } + else + { + EnterMyLock(); + lrwc = GetThreadRWCount(false); + + if (lrwc == null) + { + ExitMyLock(); + throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedWrite); + } + + if (lrwc.writercount < 1) + { + ExitMyLock(); + throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedWrite); + } + + lrwc.writercount--; + + if (lrwc.writercount > 0) + { + ExitMyLock(); + 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); + } + EnterMyLock(); + } + else + { + EnterMyLock(); + lrwc = GetThreadRWCount(true); + + if (lrwc == null) + { + ExitMyLock(); + throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedUpgrade); + } + + if (lrwc.upgradecount < 1) + { + ExitMyLock(); + throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedUpgrade); + } + + lrwc.upgradecount--; + + if (lrwc.upgradecount > 0) + { + ExitMyLock(); + return; + } + + _fUpgradeThreadHoldingRead = false; + } + + _owners--; + _upgradeLockOwnerId = -1; + + ExitAndWakeUpAppropriateWaiters(); + } + + /// + /// 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' + /// + private void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent) + { +#if DEBUG + Debug.Assert(MyLockHeld); + Debug.Assert(waitEvent == null); +#endif + ExitMyLock(); + EventWaitHandle newEvent; + if (makeAutoResetEvent) + newEvent = new AutoResetEvent(false); + else + newEvent = new ManualResetEvent(false); + EnterMyLock(); + if (waitEvent == null) // maybe someone snuck in. + waitEvent = newEvent; + else + newEvent.Dispose(); + } + + /// + /// Waits on 'waitEvent' with a timeout + /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine. + /// + private bool WaitOnEvent( + EventWaitHandle waitEvent, + ref uint numWaiters, + TimeoutTracker timeout, + bool isWriteWaiter) + { +#if DEBUG + Debug.Assert(MyLockHeld); +#endif + waitEvent.Reset(); + numWaiters++; + _fNoWaiters = false; + + //Setting these bits will prevent new readers from getting in. + if (_numWriteWaiters == 1) + SetWritersWaiting(); + if (_numWriteUpgradeWaiters == 1) + SetUpgraderWaiting(); + + bool waitSuccessful = false; + ExitMyLock(); // Do the wait outside of any lock + + try + { + waitSuccessful = waitEvent.WaitOne(timeout.RemainingMilliseconds); + } + finally + { + EnterMyLock(); + --numWaiters; + + if (_numWriteWaiters == 0 && _numWriteUpgradeWaiters == 0 && _numUpgradeWaiters == 0 && _numReadWaiters == 0) + _fNoWaiters = true; + + if (_numWriteWaiters == 0) + ClearWritersWaiting(); + if (_numWriteUpgradeWaiters == 0) + ClearUpgraderWaiting(); + + if (!waitSuccessful) // We may also be about to throw for some reason. Exit myLock. + { + if (isWriteWaiter) + { + // 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 + ExitMyLock(); + } + } + return waitSuccessful; + } + + /// + /// Determines the appropriate events to set, leaves the locks, and sets the events. + /// + private void ExitAndWakeUpAppropriateWaiters() + { +#if DEBUG + Debug.Assert(MyLockHeld); +#endif + if (_fNoWaiters) + { + ExitMyLock(); + 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) + { + ExitMyLock(); // 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. + + ExitMyLock(); // 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) + { + ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock) + _writeEvent.Set(); // release one writer. + } + else + { + ExitAndWakeUpAppropriateReadWaiters(); + } + } + + private void ExitAndWakeUpAppropriateReadWaiters() + { +#if DEBUG + Debug.Assert(MyLockHeld); +#endif + + if (_numWriteWaiters != 0 || _numWriteUpgradeWaiters != 0 || _fNoWaiters) + { + ExitMyLock(); + return; + } + + Debug.Assert(_numReadWaiters != 0 || _numUpgradeWaiters != 0); + + bool setReadEvent = _numReadWaiters != 0; + bool setUpgradeEvent = _numUpgradeWaiters != 0 && _upgradeLockOwnerId == -1; + + ExitMyLock(); // 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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnterMyLock() + { + if (Interlocked.CompareExchange(ref _myLock, 1, 0) != 0) + EnterMyLockSpin(); + } + + private void EnterMyLockSpin() + { + int pc = Environment.ProcessorCount; + for (int i = 0; ; i++) + { + if (i < LockSpinCount && pc > 1) + { + RuntimeThread.SpinWait(LockSpinCycles * (i + 1)); // Wait a few dozen instructions to let another processor release lock. + } + else if (i < (LockSpinCount + LockSleep0Count)) + { + RuntimeThread.Sleep(0); // Give up my quantum. + } + else + { + RuntimeThread.Sleep(1); // Give up my quantum. + } + + if (_myLock == 0 && Interlocked.CompareExchange(ref _myLock, 1, 0) == 0) + return; + } + } + + private void ExitMyLock() + { + Debug.Assert(_myLock != 0, "Exiting spin lock that is not held"); + Volatile.Write(ref _myLock, 0); + } + +#if DEBUG + private bool MyLockHeld { get { return _myLock != 0; } } +#endif + + private static void SpinWait(int SpinCount) + { + //Exponential back-off + if ((SpinCount < 5) && (Environment.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 artifical + // 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; + } + } + } +}