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)
128 return TryEnterReadLock (millisecondsTimeout, ref dummy);
131 public bool TryEnterReadLock (int millisecondsTimeout, ref bool success)
133 ThreadLockState ctstate = CurrentThreadState;
135 if (CheckState (ctstate, millisecondsTimeout, LockState.Read)) {
136 ++ctstate.ReaderRecursiveCount;
140 // This is downgrading from upgradable, no need for check since
141 // we already have a sort-of read lock that's going to disappear
142 // after user calls ExitUpgradeableReadLock.
143 // Same idea when recursion is allowed and a write thread wants to
144 // go for a Read too.
145 if (ctstate.LockState.Has (LockState.Upgradable)
146 || (!noRecursion && ctstate.LockState.Has (LockState.Write))) {
147 RuntimeHelpers.PrepareConstrainedRegions ();
150 Interlocked.Add (ref rwlock, RwRead);
151 ctstate.LockState |= LockState.Read;
152 ++ctstate.ReaderRecursiveCount;
161 long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
164 /* Check if a writer is present (RwWrite) or if there is someone waiting to
165 * acquire a writer lock in the queue (RwWait | RwWaitUpgrade).
167 if ((rwlock & (RwWrite | RwWait | RwWaitUpgrade)) > 0) {
168 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
172 /* Optimistically try to add ourselves to the reader value
173 * if the adding was too late and another writer came in between
174 * we revert the operation.
176 RuntimeHelpers.PrepareConstrainedRegions ();
179 if (((val = Interlocked.Add (ref rwlock, RwRead)) & (RwWrite | RwWait | RwWaitUpgrade)) == 0) {
180 /* If we are the first reader, reset the event to let other threads
181 * sleep correctly if they try to acquire write lock
183 if (val >> RwReadBit == 1)
184 readerDoneEvent.Reset ();
186 ctstate.LockState ^= LockState.Read;
187 ++ctstate.ReaderRecursiveCount;
191 Interlocked.Add (ref rwlock, -RwRead);
197 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
198 } while (millisecondsTimeout == -1 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout);
204 public bool TryEnterReadLock (TimeSpan timeout)
206 return TryEnterReadLock (CheckTimeout (timeout));
209 public void ExitReadLock ()
211 RuntimeHelpers.PrepareConstrainedRegions ();
214 ThreadLockState ctstate = CurrentThreadState;
216 if (!ctstate.LockState.Has (LockState.Read))
217 throw new SynchronizationLockException ("The current thread has not entered the lock in read mode");
219 if (--ctstate.ReaderRecursiveCount == 0) {
220 ctstate.LockState ^= LockState.Read;
221 if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
222 readerDoneEvent.Set ();
227 public void EnterWriteLock ()
229 TryEnterWriteLock (-1);
232 public bool TryEnterWriteLock (int millisecondsTimeout)
234 ThreadLockState ctstate = CurrentThreadState;
236 if (CheckState (ctstate, millisecondsTimeout, LockState.Write)) {
237 ++ctstate.WriterRecursiveCount;
242 bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
243 bool registered = false;
244 bool success = false;
246 RuntimeHelpers.PrepareConstrainedRegions ();
248 /* If the code goes there that means we had a read lock beforehand
249 * that need to be suppressed, we also take the opportunity to register
250 * our interest in the write lock to avoid other write wannabe process
251 * coming in the middle
253 if (isUpgradable && rwlock >= RwRead) {
256 if (Interlocked.Add (ref rwlock, RwWaitUpgrade - RwRead) >> RwReadBit == 0)
257 readerDoneEvent.Set ();
262 int stateCheck = isUpgradable ? RwWaitUpgrade + RwWait : RwWait;
263 long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
268 if (state <= stateCheck) {
271 if (Interlocked.CompareExchange (ref rwlock, RwWrite, state) == state) {
272 writerDoneEvent.Reset ();
273 ctstate.LockState ^= LockState.Write;
274 ++ctstate.WriterRecursiveCount;
286 // We register our interest in taking the Write lock (if upgradeable it's already done)
288 while ((state & RwWait) == 0) {
291 if (Interlocked.CompareExchange (ref rwlock, state | RwWait, state) == state)
300 // Before falling to sleep
302 if (rwlock <= stateCheck)
304 if ((rwlock & RwWrite) != 0)
305 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
306 else if ((rwlock >> RwReadBit) > 0)
307 readerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
308 } while (millisecondsTimeout < 0 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout);
309 } while (millisecondsTimeout < 0 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout);
314 Interlocked.Add (ref rwlock, isUpgradable ? -RwWaitUpgrade : -RwWait);
320 public bool TryEnterWriteLock (TimeSpan timeout)
322 return TryEnterWriteLock (CheckTimeout (timeout));
325 public void ExitWriteLock ()
327 RuntimeHelpers.PrepareConstrainedRegions ();
330 ThreadLockState ctstate = CurrentThreadState;
332 if (!ctstate.LockState.Has (LockState.Write))
333 throw new SynchronizationLockException ("The current thread has not entered the lock in write mode");
335 if (--ctstate.WriterRecursiveCount == 0) {
336 bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
337 ctstate.LockState ^= LockState.Write;
339 int value = Interlocked.Add (ref rwlock, isUpgradable ? RwRead - RwWrite : -RwWrite);
340 writerDoneEvent.Set ();
341 if (isUpgradable && value >> RwReadBit == 1)
342 readerDoneEvent.Reset ();
347 public void EnterUpgradeableReadLock ()
349 TryEnterUpgradeableReadLock (-1);
353 // Taking the Upgradable read lock is like taking a read lock
354 // but we limit it to a single upgradable at a time.
356 public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
358 ThreadLockState ctstate = CurrentThreadState;
360 if (CheckState (ctstate, millisecondsTimeout, LockState.Upgradable)) {
361 ++ctstate.UpgradeableRecursiveCount;
365 if (ctstate.LockState.Has (LockState.Read))
366 throw new LockRecursionException ("The current thread has already entered read mode");
369 long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
371 bool success = false;
373 // We first try to obtain the upgradeable right
375 while (!upgradableEvent.IsSet () || !taken) {
378 taken = upgradableTaken.TryRelaxedSet ();
382 if (millisecondsTimeout != -1 && (sw.ElapsedMilliseconds - start) > millisecondsTimeout) {
387 upgradableEvent.Wait (ComputeTimeout (millisecondsTimeout, start));
390 upgradableEvent.Reset ();
392 RuntimeHelpers.PrepareConstrainedRegions ();
394 // Then it's a simple reader lock acquiring
395 TryEnterReadLock (ComputeTimeout (millisecondsTimeout, start), ref success);
398 ctstate.LockState |= LockState.Upgradable;
399 ctstate.LockState &= ~LockState.Read;
400 --ctstate.ReaderRecursiveCount;
401 ++ctstate.UpgradeableRecursiveCount;
403 upgradableTaken.Value = false;
404 upgradableEvent.Set ();
410 // An async exception occured, if we had taken the upgradable mode, release it
411 if (taken && !success)
412 upgradableTaken.Value = false;
418 public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
420 return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
423 public void ExitUpgradeableReadLock ()
425 RuntimeHelpers.PrepareConstrainedRegions ();
428 ThreadLockState ctstate = CurrentThreadState;
430 if (!ctstate.LockState.Has (LockState.Upgradable | LockState.Read))
431 throw new SynchronizationLockException ("The current thread has not entered the lock in upgradable mode");
433 if (--ctstate.UpgradeableRecursiveCount == 0) {
434 upgradableTaken.Value = false;
435 upgradableEvent.Set ();
437 ctstate.LockState &= ~LockState.Upgradable;
438 if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
439 readerDoneEvent.Set ();
445 public void Dispose ()
450 public bool IsReadLockHeld {
452 return rwlock >= RwRead && CurrentThreadState.LockState.Has (LockState.Read);
456 public bool IsWriteLockHeld {
458 return (rwlock & RwWrite) > 0 && CurrentThreadState.LockState.Has (LockState.Write);
462 public bool IsUpgradeableReadLockHeld {
464 return upgradableTaken.Value && CurrentThreadState.LockState.Has (LockState.Upgradable);
468 public int CurrentReadCount {
470 return (rwlock >> RwReadBit) - (upgradableTaken.Value ? 1 : 0);
474 public int RecursiveReadCount {
476 return CurrentThreadState.ReaderRecursiveCount;
480 public int RecursiveUpgradeCount {
482 return CurrentThreadState.UpgradeableRecursiveCount;
486 public int RecursiveWriteCount {
488 return CurrentThreadState.WriterRecursiveCount;
492 public int WaitingReadCount {
494 return numReadWaiters;
498 public int WaitingUpgradeCount {
500 return numUpgradeWaiters;
504 public int WaitingWriteCount {
506 return numWriteWaiters;
510 public LockRecursionPolicy RecursionPolicy {
512 return recursionPolicy;
516 ThreadLockState CurrentThreadState {
518 int tid = Thread.CurrentThread.ManagedThreadId;
520 if (tid < fastStateCache.Length)
521 return fastStateCache[tid] == null ? (fastStateCache[tid] = new ThreadLockState ()) : fastStateCache[tid];
523 if (currentThreadState == null)
524 currentThreadState = new Dictionary<int, ThreadLockState> ();
526 ThreadLockState state;
527 if (!currentThreadState.TryGetValue (id, out state))
528 currentThreadState[id] = state = new ThreadLockState ();
534 bool CheckState (ThreadLockState state, int millisecondsTimeout, LockState validState)
537 throw new ObjectDisposedException ("ReaderWriterLockSlim");
539 if (millisecondsTimeout < -1)
540 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
542 // Detect and prevent recursion
543 LockState ctstate = state.LockState;
545 if (ctstate != LockState.None && noRecursion && (!ctstate.Has (LockState.Upgradable) || validState == LockState.Upgradable))
546 throw new LockRecursionException ("The current thread has already a lock and recursion isn't supported");
551 // If we already had right lock state, just return
552 if (ctstate.Has (validState))
555 CheckRecursionAuthorization (ctstate, validState);
560 static void CheckRecursionAuthorization (LockState ctstate, LockState desiredState)
562 // In read mode you can just enter Read recursively
563 if (ctstate == LockState.Read)
564 throw new LockRecursionException ();
567 static int CheckTimeout (TimeSpan timeout)
570 return checked ((int)timeout.TotalMilliseconds);
571 } catch (System.OverflowException) {
572 throw new ArgumentOutOfRangeException ("timeout");
576 static int ComputeTimeout (int millisecondsTimeout, long start)
578 return millisecondsTimeout == -1 ? -1 : (int)Math.Max (sw.ElapsedMilliseconds - start - millisecondsTimeout, 1);