Merge pull request #439 from mono-soc-2012/garyb/iconfix
[mono.git] / mcs / class / System.Core / System.Threading / ReaderWriterLockSlim.cs
index f4668d95f2658730265f4eb1c64e9163a9191295..512f97b27cb15619cd97894d61102b3cb895806d 100644 (file)
@@ -32,6 +32,7 @@ using System.Collections.Generic;
 using System.Security.Permissions;
 using System.Diagnostics;
 using System.Threading;
+using System.Runtime.CompilerServices;
 
 namespace System.Threading {
 
@@ -61,6 +62,7 @@ namespace System.Threading {
                int rwlock;
                
                readonly LockRecursionPolicy recursionPolicy;
+               readonly bool noRecursion;
 
                AtomicBoolean upgradableTaken = new AtomicBoolean ();
 
@@ -98,6 +100,13 @@ namespace System.Threading {
                [ThreadStatic]
                static IDictionary<int, ThreadLockState> currentThreadState;
 
+               /* 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)
                {
                }
@@ -105,6 +114,7 @@ namespace System.Threading {
                public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
                {
                        this.recursionPolicy = recursionPolicy;
+                       this.noRecursion = recursionPolicy == LockRecursionPolicy.NoRecursion;
                }
 
                public void EnterReadLock ()
@@ -113,10 +123,16 @@ namespace System.Threading {
                }
 
                public bool TryEnterReadLock (int millisecondsTimeout)
+               {
+                       bool dummy = false;
+                       return TryEnterReadLock (millisecondsTimeout, ref dummy);
+               }
+
+               bool TryEnterReadLock (int millisecondsTimeout, ref bool success)
                {
                        ThreadLockState ctstate = CurrentThreadState;
 
-                       if (CheckState (millisecondsTimeout, LockState.Read)) {
+                       if (CheckState (ctstate, millisecondsTimeout, LockState.Read)) {
                                ++ctstate.ReaderRecursiveCount;
                                return true;
                        }
@@ -126,11 +142,16 @@ namespace System.Threading {
                        // after user calls ExitUpgradeableReadLock.
                        // Same idea when recursion is allowed and a write thread wants to
                        // go for a Read too.
-                       if (CurrentLockState.Has (LockState.Upgradable)
-                           || recursionPolicy == LockRecursionPolicy.SupportsRecursion) {
-                               Interlocked.Add (ref rwlock, RwRead);
-                               ctstate.LockState ^= LockState.Read;
-                               ++ctstate.ReaderRecursiveCount;
+                       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;
                        }
@@ -143,7 +164,7 @@ namespace System.Threading {
                                /* 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 & 0x7) > 0) {
+                               if ((rwlock & (RwWrite | RwWait | RwWaitUpgrade)) > 0) {
                                        writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
                                        continue;
                                }
@@ -152,20 +173,26 @@ namespace System.Threading {
                                 * if the adding was too late and another writer came in between
                                 * we revert the operation.
                                 */
-                               if (((val = Interlocked.Add (ref rwlock, RwRead)) & 0x7) == 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;
-                                       return true;
+                               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);
+                                       }
                                }
-
-                               Interlocked.Add (ref rwlock, -RwRead);
+                               if (success)
+                                       return true;
 
                                writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
                        } while (millisecondsTimeout == -1 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout);
@@ -181,15 +208,20 @@ namespace System.Threading {
 
                public void ExitReadLock ()
                {
-                       ThreadLockState ctstate = CurrentThreadState;
+                       RuntimeHelpers.PrepareConstrainedRegions ();
+                       try {}
+                       finally {
+                               ThreadLockState ctstate = CurrentThreadState;
 
-                       if (!ctstate.LockState.Has (LockState.Read))
-                               throw new SynchronizationLockException ("The current thread has not entered the lock in read mode");
+                               if (!ctstate.LockState.Has (LockState.Read))
+                                       throw new SynchronizationLockException ("The current thread has not entered the lock in read mode");
 
-                       ctstate.LockState ^= LockState.Read;
-                       --ctstate.ReaderRecursiveCount;
-                       if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
-                               readerDoneEvent.Set ();
+                               if (--ctstate.ReaderRecursiveCount == 0) {
+                                       ctstate.LockState ^= LockState.Read;
+                                       if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
+                                               readerDoneEvent.Set ();
+                               }
+                       }
                }
 
                public void EnterWriteLock ()
@@ -201,57 +233,89 @@ namespace System.Threading {
                {
                        ThreadLockState ctstate = CurrentThreadState;
 
-                       if (CheckState (millisecondsTimeout, LockState.Write)) {
+                       if (CheckState (ctstate, millisecondsTimeout, LockState.Write)) {
                                ++ctstate.WriterRecursiveCount;
                                return true;
                        }
 
                        ++numWriteWaiters;
                        bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
+                       bool registered = false;
+                       bool success = false;
 
-                       /* 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)
-                               if (Interlocked.Add (ref rwlock, RwWaitUpgrade - RwRead) >> RwReadBit == 0)
-                                       readerDoneEvent.Set ();
+                       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;
+                                       }
+                               }
 
-                       int stateCheck = isUpgradable ? RwWaitUpgrade : RwWait;
-                       long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
+                               int stateCheck = isUpgradable ? RwWaitUpgrade + RwWait : RwWait;
+                               long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
+                               int registration = isUpgradable ? RwWaitUpgrade : RwWait;
 
-                       do {
-                               int state = rwlock;
-
-                               if (state <= stateCheck) {
-                                       if (Interlocked.CompareExchange (ref rwlock, RwWrite, state) == state) {
-                                               writerDoneEvent.Reset ();
-                                               ctstate.LockState ^= LockState.Write;
-                                               ++ctstate.WriterRecursiveCount;
-                                               --numWriteWaiters;
-                                               return true;
+                               do {
+                                       int 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 && Interlocked.CompareExchange (ref rwlock, state | RwWait, state) != state)
-                                               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 {
+                                                               if (Interlocked.CompareExchange (ref rwlock, state | RwWait, state) == state)
+                                                                       registered = true;
+                                                       }
+                                                       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));
+                                       // 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 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout);
                                } while (millisecondsTimeout < 0 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout);
-                       } while (millisecondsTimeout < 0 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout);
 
-                       --numWriteWaiters;
+                               --numWriteWaiters;
+                       } finally {
+                               if (registered)
+                                       Interlocked.Add (ref rwlock, isUpgradable ? -RwWaitUpgrade : -RwWait);
+                       }
+
                        return false;
                }
 
@@ -262,19 +326,24 @@ namespace System.Threading {
 
                public void ExitWriteLock ()
                {
-                       ThreadLockState ctstate = CurrentThreadState;
+                       RuntimeHelpers.PrepareConstrainedRegions ();
+                       try {}
+                       finally {
+                               ThreadLockState ctstate = CurrentThreadState;
 
-                       if (!ctstate.LockState.Has (LockState.Write))
-                               throw new SynchronizationLockException ("The current thread has not entered the lock in write mode");
+                               if (!ctstate.LockState.Has (LockState.Write))
+                                       throw new SynchronizationLockException ("The current thread has not entered the lock in write mode");
                        
-                       bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
-                       ctstate.LockState ^= LockState.Write;
-                       --ctstate.WriterRecursiveCount;
+                               if (--ctstate.WriterRecursiveCount == 0) {
+                                       bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
+                                       ctstate.LockState ^= LockState.Write;
 
-                       int value = Interlocked.Add (ref rwlock, isUpgradable ? RwRead - RwWrite : -RwWrite);
-                       writerDoneEvent.Set ();
-                       if (isUpgradable && value >> RwReadBit == 1)
-                               readerDoneEvent.Reset ();
+                                       int value = Interlocked.Add (ref rwlock, isUpgradable ? RwRead - RwWrite : -RwWrite);
+                                       writerDoneEvent.Set ();
+                                       if (isUpgradable && value >> RwReadBit == 1)
+                                               readerDoneEvent.Reset ();
+                               }
+                       }
                }
 
                public void EnterUpgradeableReadLock ()
@@ -290,7 +359,7 @@ namespace System.Threading {
                {
                        ThreadLockState ctstate = CurrentThreadState;
 
-                       if (CheckState (millisecondsTimeout, LockState.Upgradable)) {
+                       if (CheckState (ctstate, millisecondsTimeout, LockState.Upgradable)) {
                                ++ctstate.UpgradeableRecursiveCount;
                                return true;
                        }
@@ -300,34 +369,52 @@ namespace System.Threading {
 
                        ++numUpgradeWaiters;
                        long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
+                       bool taken = false;
+                       bool success = false;
 
                        // We first try to obtain the upgradeable right
-                       while (!upgradableEvent.IsSet () || !upgradableTaken.TryRelaxedSet ()) {
-                               if (millisecondsTimeout != -1 && (sw.ElapsedMilliseconds - start) > millisecondsTimeout) {
-                                       --numUpgradeWaiters;
-                                       return false;
-                               }
+                       try {
+                               while (!upgradableEvent.IsSet () || !taken) {
+                                       try {}
+                                       finally {
+                                               taken = upgradableTaken.TryRelaxedSet ();
+                                       }
+                                       if (taken)
+                                               break;
+                                       if (millisecondsTimeout != -1 && (sw.ElapsedMilliseconds - start) > millisecondsTimeout) {
+                                               --numUpgradeWaiters;
+                                               return false;
+                                       }
 
-                               upgradableEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
-                       }
+                                       upgradableEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
+                               }
 
-                       upgradableEvent.Reset ();
+                               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 ();
+                                       }
+                               }
 
-                       // Then it's a simple reader lock acquiring
-                       if (TryEnterReadLock (ComputeTimeout (millisecondsTimeout, start))) {
-                               ctstate.LockState = LockState.Upgradable;
                                --numUpgradeWaiters;
-                               --ctstate.ReaderRecursiveCount;
-                               ++ctstate.UpgradeableRecursiveCount;
-                               return true;
+                       } catch {
+                               // An async exception occured, if we had taken the upgradable mode, release it
+                               if (taken && !success)
+                                       upgradableTaken.Value = false;
                        }
 
-                       upgradableTaken.Value = false;
-                       upgradableEvent.Set ();
-
-                       --numUpgradeWaiters;
-
-                       return false;
+                       return success;
                }
 
                public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
@@ -337,18 +424,24 @@ namespace System.Threading {
               
                public void ExitUpgradeableReadLock ()
                {
-                       ThreadLockState ctstate = CurrentThreadState;
+                       RuntimeHelpers.PrepareConstrainedRegions ();
+                       try {}
+                       finally {
+                               ThreadLockState ctstate = CurrentThreadState;
 
-                       if (!ctstate.LockState.Has (LockState.Upgradable | LockState.Read))
-                               throw new SynchronizationLockException ("The current thread has not entered the lock in upgradable mode");
-                       
-                       upgradableTaken.Value = false;
-                       upgradableEvent.Set ();
+                               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 ();
+                               }
+                       }
 
-                       ctstate.LockState ^= LockState.Upgradable;
-                       --ctstate.UpgradeableRecursiveCount;
-                       if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
-                               readerDoneEvent.Set ();
                }
 
                public void Dispose ()
@@ -361,7 +454,7 @@ namespace System.Threading {
                                return rwlock >= RwRead && CurrentThreadState.LockState.Has (LockState.Read);
                        }
                }
-               
+
                public bool IsWriteLockHeld {
                        get {
                                return (rwlock & RwWrite) > 0 && CurrentThreadState.LockState.Has (LockState.Write);
@@ -422,17 +515,13 @@ namespace System.Threading {
                        }
                }
 
-               LockState CurrentLockState {
-                       get {
-                               return CurrentThreadState.LockState;
-                       }
-                       set {
-                               CurrentThreadState.LockState = value;
-                       }
-               }
-
                ThreadLockState CurrentThreadState {
                        get {
+                               int tid = Thread.CurrentThread.ManagedThreadId;
+
+                               if (tid < fastStateCache.Length)
+                                       return fastStateCache[tid] == null ? (fastStateCache[tid] = new ThreadLockState ()) : fastStateCache[tid];
+
                                if (currentThreadState == null)
                                        currentThreadState = new Dictionary<int, ThreadLockState> ();
 
@@ -444,21 +533,22 @@ namespace System.Threading {
                        }
                }
 
-               bool CheckState (int millisecondsTimeout, LockState validState)
+               bool CheckState (ThreadLockState state, int millisecondsTimeout, LockState validState)
                {
                        if (disposed)
                                throw new ObjectDisposedException ("ReaderWriterLockSlim");
 
-                       if (millisecondsTimeout < Timeout.Infinite)
+                       if (millisecondsTimeout < -1)
                                throw new ArgumentOutOfRangeException ("millisecondsTimeout");
 
                        // Detect and prevent recursion
-                       LockState ctstate = CurrentLockState;
+                       LockState 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 (recursionPolicy == LockRecursionPolicy.NoRecursion)
-                               if ((ctstate != LockState.None && ctstate != LockState.Upgradable)
-                                   || (ctstate == 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))