2 // System.Threading.ReaderWriterLockSlim.cs
5 // Jérémie "Garuma" Laval <jeremie.laval@gmail.com>
7 // Copyright (c) 2010 Jérémie "Garuma" Laval
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Security.Permissions;
33 using System.Diagnostics;
34 using System.Threading;
35 using System.Runtime.CompilerServices;
37 namespace System.Threading {
39 [HostProtectionAttribute(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
40 [HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
41 public class ReaderWriterLockSlim : IDisposable
43 /* Position of each bit isn't really important
44 * but their relative order is
46 const int RwReadBit = 3;
48 /* These values are used to manipulate the corresponding flags in rwlock field
51 const int RwWaitUpgrade = 2;
52 const int RwWrite = 4;
55 /* Some explanations: this field is the central point of the lock and keep track of all the requests
56 * that are being made. The 3 lowest bits are used as flag to track "destructive" lock entries
57 * (i.e attempting to take the write lock with or without having acquired an upgradeable lock beforehand).
58 * All the remaining bits are intepreted as the actual number of reader currently using the lock
59 * (which mean the lock is limited to 4294967288 concurrent readers but since it's a high number there
60 * is no overflow safe guard to remain simple).
64 readonly LockRecursionPolicy recursionPolicy;
65 readonly bool noRecursion;
67 AtomicBoolean upgradableTaken = new AtomicBoolean ();
69 /* These events are just here for the sake of having a CPU-efficient sleep
70 * when the wait for acquiring the lock is too long
73 ManualResetEventSlim upgradableEvent = new ManualResetEventSlim (true);
74 ManualResetEventSlim writerDoneEvent = new ManualResetEventSlim (true);
75 ManualResetEventSlim readerDoneEvent = new ManualResetEventSlim (true);
77 ManualResetEvent upgradableEvent = new ManualResetEvent (true);
78 ManualResetEvent writerDoneEvent = new ManualResetEvent (true);
79 ManualResetEvent readerDoneEvent = new ManualResetEvent (true);
82 // This Stopwatch instance is used for all threads since .Elapsed is thread-safe
83 readonly static Stopwatch sw = Stopwatch.StartNew ();
85 /* For performance sake, these numbers are manipulated via classic increment and
86 * decrement operations and thus are (as hinted by MSDN) not meant to be precise
88 int numReadWaiters, numUpgradeWaiters, numWriteWaiters;
91 static int idPool = int.MinValue;
92 readonly int id = Interlocked.Increment (ref idPool);
94 /* This dictionary is instanciated per thread for all existing ReaderWriterLockSlim instance.
95 * Each instance is defined by an internal integer id value used as a key in the dictionary.
96 * to avoid keeping unneeded reference to the instance and getting in the way of the GC.
97 * Since there is no LockCookie type here, all the useful per-thread infos concerning each
98 * instance are kept here.
101 static IDictionary<int, ThreadLockState> currentThreadState;
103 /* Rwls tries to use this array as much as possible to quickly retrieve the thread-local
104 * informations so that it ends up being only an array lookup. When the number of thread
105 * using the instance goes past the length of the array, the code fallback to the normal
108 ThreadLockState[] fastStateCache = new ThreadLockState[64];
110 public ReaderWriterLockSlim () : this (LockRecursionPolicy.NoRecursion)
114 public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
116 this.recursionPolicy = recursionPolicy;
117 this.noRecursion = recursionPolicy == LockRecursionPolicy.NoRecursion;
120 public void EnterReadLock ()
122 TryEnterReadLock (-1);
125 public bool TryEnterReadLock (int millisecondsTimeout)
127 ThreadLockState ctstate = CurrentThreadState;
129 if (CheckState (ctstate, millisecondsTimeout, LockState.Read)) {
130 ++ctstate.ReaderRecursiveCount;
134 // This is downgrading from upgradable, no need for check since
135 // we already have a sort-of read lock that's going to disappear
136 // after user calls ExitUpgradeableReadLock.
137 // Same idea when recursion is allowed and a write thread wants to
138 // go for a Read too.
139 if (ctstate.LockState.Has (LockState.Upgradable)
140 || (!noRecursion && ctstate.LockState.Has (LockState.Write))) {
141 RuntimeHelpers.PrepareConstrainedRegions ();
144 Interlocked.Add (ref rwlock, RwRead);
145 ctstate.LockState ^= LockState.Read;
146 ++ctstate.ReaderRecursiveCount;
154 long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
155 bool success = false;
158 /* Check if a writer is present (RwWrite) or if there is someone waiting to
159 * acquire a writer lock in the queue (RwWait | RwWaitUpgrade).
161 if ((rwlock & (RwWrite | RwWait | RwWaitUpgrade)) > 0) {
162 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
166 /* Optimistically try to add ourselves to the reader value
167 * if the adding was too late and another writer came in between
168 * we revert the operation.
170 RuntimeHelpers.PrepareConstrainedRegions ();
173 if (((val = Interlocked.Add (ref rwlock, RwRead)) & (RwWrite | RwWait | RwWaitUpgrade)) == 0) {
174 /* If we are the first reader, reset the event to let other threads
175 * sleep correctly if they try to acquire write lock
177 if (val >> RwReadBit == 1)
178 readerDoneEvent.Reset ();
180 ctstate.LockState ^= LockState.Read;
181 ++ctstate.ReaderRecursiveCount;
185 Interlocked.Add (ref rwlock, -RwRead);
191 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
192 } while (millisecondsTimeout == -1 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout);
198 public bool TryEnterReadLock (TimeSpan timeout)
200 return TryEnterReadLock (CheckTimeout (timeout));
203 public void ExitReadLock ()
205 RuntimeHelpers.PrepareConstrainedRegions ();
208 ThreadLockState ctstate = CurrentThreadState;
210 if (!ctstate.LockState.Has (LockState.Read))
211 throw new SynchronizationLockException ("The current thread has not entered the lock in read mode");
213 if (--ctstate.ReaderRecursiveCount == 0) {
214 ctstate.LockState ^= LockState.Read;
215 if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
216 readerDoneEvent.Set ();
221 public void EnterWriteLock ()
223 TryEnterWriteLock (-1);
226 public bool TryEnterWriteLock (int millisecondsTimeout)
228 ThreadLockState ctstate = CurrentThreadState;
230 if (CheckState (ctstate, millisecondsTimeout, LockState.Write)) {
231 ++ctstate.WriterRecursiveCount;
236 bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
237 bool registered = false;
238 bool success = false;
240 RuntimeHelpers.PrepareConstrainedRegions ();
242 /* If the code goes there that means we had a read lock beforehand
243 * that need to be suppressed, we also take the opportunity to register
244 * our interest in the write lock to avoid other write wannabe process
245 * coming in the middle
247 if (isUpgradable && rwlock >= RwRead) {
250 if (Interlocked.Add (ref rwlock, RwWaitUpgrade - RwRead) >> RwReadBit == 0)
251 readerDoneEvent.Set ();
256 int stateCheck = isUpgradable ? RwWaitUpgrade : RwWait;
257 long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
262 if (state <= stateCheck) {
265 if (Interlocked.CompareExchange (ref rwlock, RwWrite, state) == state) {
266 writerDoneEvent.Reset ();
267 ctstate.LockState ^= LockState.Write;
268 ++ctstate.WriterRecursiveCount;
280 // We register our interest in taking the Write lock (if upgradeable it's already done)
282 while ((state & RwWait) == 0) {
285 if (Interlocked.CompareExchange (ref rwlock, state | RwWait, state) == state)
294 // Before falling to sleep
296 if (rwlock <= stateCheck)
298 if ((rwlock & RwWrite) != 0)
299 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
300 else if ((rwlock >> RwReadBit) > 0)
301 readerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
302 } while (millisecondsTimeout < 0 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout);
303 } while (millisecondsTimeout < 0 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout);
308 Interlocked.Add (ref rwlock, isUpgradable ? -RwWaitUpgrade : -RwWait);
314 public bool TryEnterWriteLock (TimeSpan timeout)
316 return TryEnterWriteLock (CheckTimeout (timeout));
319 public void ExitWriteLock ()
321 RuntimeHelpers.PrepareConstrainedRegions ();
324 ThreadLockState ctstate = CurrentThreadState;
326 if (!ctstate.LockState.Has (LockState.Write))
327 throw new SynchronizationLockException ("The current thread has not entered the lock in write mode");
329 if (--ctstate.WriterRecursiveCount == 0) {
330 bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
331 ctstate.LockState ^= LockState.Write;
333 int value = Interlocked.Add (ref rwlock, isUpgradable ? RwRead - RwWrite : -RwWrite);
334 writerDoneEvent.Set ();
335 if (isUpgradable && value >> RwReadBit == 1)
336 readerDoneEvent.Reset ();
341 public void EnterUpgradeableReadLock ()
343 TryEnterUpgradeableReadLock (-1);
347 // Taking the Upgradable read lock is like taking a read lock
348 // but we limit it to a single upgradable at a time.
350 public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
352 ThreadLockState ctstate = CurrentThreadState;
354 if (CheckState (ctstate, millisecondsTimeout, LockState.Upgradable)) {
355 ++ctstate.UpgradeableRecursiveCount;
359 if (ctstate.LockState.Has (LockState.Read))
360 throw new LockRecursionException ("The current thread has already entered read mode");
363 long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
365 // We first try to obtain the upgradeable right
366 while (!upgradableEvent.IsSet () || !upgradableTaken.TryRelaxedSet ()) {
367 if (millisecondsTimeout != -1 && (sw.ElapsedMilliseconds - start) > millisecondsTimeout) {
372 upgradableEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
375 upgradableEvent.Reset ();
377 // Then it's a simple reader lock acquiring
378 if (TryEnterReadLock (ComputeTimeout (millisecondsTimeout, start))) {
379 ctstate.LockState = LockState.Upgradable;
381 --ctstate.ReaderRecursiveCount;
382 ++ctstate.UpgradeableRecursiveCount;
386 upgradableTaken.Value = false;
387 upgradableEvent.Set ();
394 public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
396 return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
399 public void ExitUpgradeableReadLock ()
401 RuntimeHelpers.PrepareConstrainedRegions ();
404 ThreadLockState ctstate = CurrentThreadState;
406 if (!ctstate.LockState.Has (LockState.Upgradable | LockState.Read))
407 throw new SynchronizationLockException ("The current thread has not entered the lock in upgradable mode");
409 if (--ctstate.UpgradeableRecursiveCount == 0) {
410 upgradableTaken.Value = false;
411 upgradableEvent.Set ();
413 ctstate.LockState ^= LockState.Upgradable;
414 if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
415 readerDoneEvent.Set ();
420 public void Dispose ()
425 public bool IsReadLockHeld {
427 return rwlock >= RwRead && CurrentThreadState.LockState.Has (LockState.Read);
431 public bool IsWriteLockHeld {
433 return (rwlock & RwWrite) > 0 && CurrentThreadState.LockState.Has (LockState.Write);
437 public bool IsUpgradeableReadLockHeld {
439 return upgradableTaken.Value && CurrentThreadState.LockState.Has (LockState.Upgradable);
443 public int CurrentReadCount {
445 return (rwlock >> RwReadBit) - (upgradableTaken.Value ? 1 : 0);
449 public int RecursiveReadCount {
451 return CurrentThreadState.ReaderRecursiveCount;
455 public int RecursiveUpgradeCount {
457 return CurrentThreadState.UpgradeableRecursiveCount;
461 public int RecursiveWriteCount {
463 return CurrentThreadState.WriterRecursiveCount;
467 public int WaitingReadCount {
469 return numReadWaiters;
473 public int WaitingUpgradeCount {
475 return numUpgradeWaiters;
479 public int WaitingWriteCount {
481 return numWriteWaiters;
485 public LockRecursionPolicy RecursionPolicy {
487 return recursionPolicy;
491 ThreadLockState CurrentThreadState {
493 int tid = Thread.CurrentThread.ManagedThreadId;
495 if (tid < fastStateCache.Length)
496 return fastStateCache[tid] == null ? (fastStateCache[tid] = new ThreadLockState ()) : fastStateCache[tid];
498 if (currentThreadState == null)
499 currentThreadState = new Dictionary<int, ThreadLockState> ();
501 ThreadLockState state;
502 if (!currentThreadState.TryGetValue (id, out state))
503 currentThreadState[id] = state = new ThreadLockState ();
509 bool CheckState (ThreadLockState state, int millisecondsTimeout, LockState validState)
512 throw new ObjectDisposedException ("ReaderWriterLockSlim");
514 if (millisecondsTimeout < -1)
515 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
517 // Detect and prevent recursion
518 LockState ctstate = state.LockState;
520 if (ctstate != LockState.None && noRecursion && (ctstate != LockState.Upgradable || validState == LockState.Upgradable))
521 throw new LockRecursionException ("The current thread has already a lock and recursion isn't supported");
526 // If we already had right lock state, just return
527 if (ctstate.Has (validState))
530 CheckRecursionAuthorization (ctstate, validState);
535 static void CheckRecursionAuthorization (LockState ctstate, LockState desiredState)
537 // In read mode you can just enter Read recursively
538 if (ctstate == LockState.Read)
539 throw new LockRecursionException ();
542 static int CheckTimeout (TimeSpan timeout)
545 return checked ((int)timeout.TotalMilliseconds);
546 } catch (System.OverflowException) {
547 throw new ArgumentOutOfRangeException ("timeout");
551 static int ComputeTimeout (int millisecondsTimeout, long start)
553 return millisecondsTimeout == -1 ? -1 : (int)Math.Max (sw.ElapsedMilliseconds - start - millisecondsTimeout, 1);