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;
36 namespace System.Threading {
38 [HostProtectionAttribute(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
39 [HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
40 public class ReaderWriterLockSlim : IDisposable
42 /* Position of each bit isn't really important
43 * but their relative order is
45 const int RwReadBit = 3;
47 /* These values are used to manipulate the corresponding flags in rwlock field
50 const int RwWaitUpgrade = 2;
51 const int RwWrite = 4;
54 /* Some explanations: this field is the central point of the lock and keep track of all the requests
55 * that are being made. The 3 lowest bits are used as flag to track "destructive" lock entries
56 * (i.e attempting to take the write lock with or without having acquired an upgradeable lock beforehand).
57 * All the remaining bits are intepreted as the actual number of reader currently using the lock
58 * (which mean the lock is limited to 4294967288 concurrent readers but since it's a high number there
59 * is no overflow safe guard to remain simple).
63 readonly LockRecursionPolicy recursionPolicy;
65 AtomicBoolean upgradableTaken = new AtomicBoolean ();
67 /* These events are just here for the sake of having a CPU-efficient sleep
68 * when the wait for acquiring the lock is too long
71 ManualResetEventSlim upgradableEvent = new ManualResetEventSlim (true);
72 ManualResetEventSlim writerDoneEvent = new ManualResetEventSlim (true);
73 ManualResetEventSlim readerDoneEvent = new ManualResetEventSlim (true);
75 ManualResetEvent upgradableEvent = new ManualResetEvent (true);
76 ManualResetEvent writerDoneEvent = new ManualResetEvent (true);
77 ManualResetEvent readerDoneEvent = new ManualResetEvent (true);
80 int numReadWaiters, numUpgradeWaiters, numWriteWaiters;
83 static int idPool = int.MinValue;
84 readonly int id = Interlocked.Increment (ref idPool);
87 static IDictionary<int, ThreadLockState> currentThreadState;
89 public ReaderWriterLockSlim () : this (LockRecursionPolicy.NoRecursion)
93 public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
95 this.recursionPolicy = recursionPolicy;
98 public void EnterReadLock ()
100 TryEnterReadLock (-1);
103 public bool TryEnterReadLock (int millisecondsTimeout)
105 ThreadLockState ctstate = CurrentThreadState;
107 if (CheckState (millisecondsTimeout, LockState.Read)) {
108 ctstate.ReaderRecursiveCount++;
112 // This is downgrading from upgradable, no need for check since
113 // we already have a sort-of read lock that's going to disappear
114 // after user calls ExitUpgradeableReadLock.
115 // Same idea when recursion is allowed and a write thread wants to
116 // go for a Read too.
117 if (CurrentLockState.Has (LockState.Upgradable)
118 || recursionPolicy == LockRecursionPolicy.SupportsRecursion) {
119 Interlocked.Add (ref rwlock, RwRead);
120 ctstate.LockState ^= LockState.Read;
121 ctstate.ReaderRecursiveCount++;
126 Stopwatch sw = Stopwatch.StartNew ();
127 Interlocked.Increment (ref numReadWaiters);
130 while (millisecondsTimeout == -1 || sw.ElapsedMilliseconds < millisecondsTimeout) {
131 /* Check if a writer is present (RwWrite) or if there is someone waiting to
132 * acquire a writer lock in the queue (RwWait | RwWaitUpgrade).
134 if ((rwlock & 0x7) > 0) {
135 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
139 /* Optimistically try to add ourselves to the reader value
140 * if the adding was too late and another writer came in between
141 * we revert the operation.
143 if (((val = Interlocked.Add (ref rwlock, RwRead)) & 0x7) == 0) {
144 /* If we are the first reader, reset the event to let other threads
145 * sleep correctly if they try to acquire write lock
147 if (val >> RwReadBit == 1)
148 readerDoneEvent.Reset ();
150 ctstate.LockState ^= LockState.Read;
151 ctstate.ReaderRecursiveCount++;
152 Interlocked.Decrement (ref numReadWaiters);
156 Interlocked.Add (ref rwlock, -RwRead);
158 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
161 Interlocked.Decrement (ref numReadWaiters);
165 public bool TryEnterReadLock (TimeSpan timeout)
167 return TryEnterReadLock (CheckTimeout (timeout));
170 public void ExitReadLock ()
172 ThreadLockState ctstate = CurrentThreadState;
174 if (!ctstate.LockState.Has (LockState.Read))
175 throw new SynchronizationLockException ("The current thread has not entered the lock in read mode");
177 ctstate.LockState ^= LockState.Read;
178 ctstate.ReaderRecursiveCount--;
179 if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
180 readerDoneEvent.Set ();
183 public void EnterWriteLock ()
185 TryEnterWriteLock (-1);
188 public bool TryEnterWriteLock (int millisecondsTimeout)
190 ThreadLockState ctstate = CurrentThreadState;
192 if (CheckState (millisecondsTimeout, LockState.Write)) {
193 ctstate.WriterRecursiveCount++;
197 Stopwatch sw = Stopwatch.StartNew ();
198 Interlocked.Increment (ref numWriteWaiters);
199 bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
201 /* If the code goes there that means we had a read lock beforehand
202 * that need to be suppressed, we also take the opportunity to register
203 * our interest in the write lock to avoid other write wannabe process
204 * coming in the middle
206 if (isUpgradable && rwlock >= RwRead)
207 if (Interlocked.Add (ref rwlock, RwWaitUpgrade - RwRead) >> RwReadBit == 0)
208 readerDoneEvent.Set ();
210 int stateCheck = isUpgradable ? RwWaitUpgrade : RwWait;
212 while (millisecondsTimeout < 0 || sw.ElapsedMilliseconds < millisecondsTimeout) {
215 if (state <= stateCheck) {
216 if (Interlocked.CompareExchange (ref rwlock, RwWrite, state) == state) {
217 writerDoneEvent.Reset ();
218 ctstate.LockState ^= LockState.Write;
219 ctstate.WriterRecursiveCount++;
220 Interlocked.Decrement (ref numWriteWaiters);
226 // We register our interest in taking the Write lock (if upgradeable it's already done)
228 while ((state & RwWait) == 0 && Interlocked.CompareExchange (ref rwlock, state | RwWait, state) == state)
231 // Before falling to sleep
232 while (rwlock > stateCheck && (millisecondsTimeout < 0 || sw.ElapsedMilliseconds < millisecondsTimeout)) {
233 if ((rwlock & RwWrite) != 0)
234 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
235 else if ((rwlock >> RwReadBit) > 0)
236 readerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
240 Interlocked.Decrement (ref numWriteWaiters);
244 public bool TryEnterWriteLock (TimeSpan timeout)
246 return TryEnterWriteLock (CheckTimeout (timeout));
249 public void ExitWriteLock ()
251 ThreadLockState ctstate = CurrentThreadState;
253 if (!ctstate.LockState.Has (LockState.Write))
254 throw new SynchronizationLockException ("The current thread has not entered the lock in write mode");
256 bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
257 ctstate.LockState ^= LockState.Write;
258 ctstate.WriterRecursiveCount--;
260 int value = Interlocked.Add (ref rwlock, isUpgradable ? RwRead - RwWrite : -RwWrite);
261 writerDoneEvent.Set ();
262 if (isUpgradable && value >> RwReadBit == 1)
263 readerDoneEvent.Reset ();
266 public void EnterUpgradeableReadLock ()
268 TryEnterUpgradeableReadLock (-1);
272 // Taking the Upgradable read lock is like taking a read lock
273 // but we limit it to a single upgradable at a time.
275 public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
277 ThreadLockState ctstate = CurrentThreadState;
279 if (CheckState (millisecondsTimeout, LockState.Upgradable)) {
280 ctstate.UpgradeableRecursiveCount++;
284 if (ctstate.LockState.Has (LockState.Read))
285 throw new LockRecursionException ("The current thread has already entered read mode");
287 Stopwatch sw = Stopwatch.StartNew ();
288 Interlocked.Increment (ref numUpgradeWaiters);
290 // We first try to obtain the upgradeable right
291 while (!upgradableEvent.IsSet () || !upgradableTaken.TryRelaxedSet ()) {
292 if (millisecondsTimeout != -1 && sw.ElapsedMilliseconds > millisecondsTimeout) {
293 Interlocked.Decrement (ref numUpgradeWaiters);
297 upgradableEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
300 upgradableEvent.Reset ();
302 // Then it's a simple reader lock acquiring
303 if (TryEnterReadLock (ComputeTimeout (millisecondsTimeout, sw))) {
304 ctstate.LockState = LockState.Upgradable;
305 Interlocked.Decrement (ref numUpgradeWaiters);
306 ctstate.ReaderRecursiveCount--;
307 ctstate.UpgradeableRecursiveCount++;
311 upgradableTaken.Value = false;
312 upgradableEvent.Set ();
314 Interlocked.Decrement (ref numUpgradeWaiters);
319 public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
321 return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
324 public void ExitUpgradeableReadLock ()
326 ThreadLockState ctstate = CurrentThreadState;
328 if (!ctstate.LockState.Has (LockState.Upgradable | LockState.Read))
329 throw new SynchronizationLockException ("The current thread has not entered the lock in upgradable mode");
331 upgradableTaken.Value = false;
332 upgradableEvent.Set ();
334 ctstate.LockState ^= LockState.Upgradable;
335 ctstate.UpgradeableRecursiveCount--;
336 if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
337 readerDoneEvent.Set ();
340 public void Dispose ()
345 public bool IsReadLockHeld {
347 return rwlock >= RwRead && CurrentThreadState.LockState.Has (LockState.Read);
351 public bool IsWriteLockHeld {
353 return (rwlock & RwWrite) > 0 && CurrentThreadState.LockState.Has (LockState.Write);
357 public bool IsUpgradeableReadLockHeld {
359 return upgradableTaken.Value && CurrentThreadState.LockState.Has (LockState.Upgradable);
363 public int CurrentReadCount {
365 return (rwlock >> RwReadBit) - (upgradableTaken.Value ? 1 : 0);
369 public int RecursiveReadCount {
371 return CurrentThreadState.ReaderRecursiveCount;
375 public int RecursiveUpgradeCount {
377 return CurrentThreadState.UpgradeableRecursiveCount;
381 public int RecursiveWriteCount {
383 return CurrentThreadState.WriterRecursiveCount;
387 public int WaitingReadCount {
389 return numReadWaiters;
393 public int WaitingUpgradeCount {
395 return numUpgradeWaiters;
399 public int WaitingWriteCount {
401 return numWriteWaiters;
405 public LockRecursionPolicy RecursionPolicy {
407 return recursionPolicy;
411 LockState CurrentLockState {
413 return CurrentThreadState.LockState;
416 CurrentThreadState.LockState = value;
420 ThreadLockState CurrentThreadState {
422 if (currentThreadState == null)
423 currentThreadState = new Dictionary<int, ThreadLockState> ();
425 ThreadLockState state;
426 if (!currentThreadState.TryGetValue (id, out state))
427 currentThreadState[id] = state = new ThreadLockState ();
433 bool CheckState (int millisecondsTimeout, LockState validState)
436 throw new ObjectDisposedException ("ReaderWriterLockSlim");
438 if (millisecondsTimeout < Timeout.Infinite)
439 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
441 // Detect and prevent recursion
442 LockState ctstate = CurrentLockState;
444 if (recursionPolicy == LockRecursionPolicy.NoRecursion)
445 if ((ctstate != LockState.None && ctstate != LockState.Upgradable)
446 || (ctstate == LockState.Upgradable && validState == LockState.Upgradable))
447 throw new LockRecursionException ("The current thread has already a lock and recursion isn't supported");
449 // If we already had right lock state, just return
450 if (ctstate.Has (validState))
453 CheckRecursionAuthorization (ctstate, validState);
458 static void CheckRecursionAuthorization (LockState ctstate, LockState desiredState)
460 // In read mode you can just enter Read recursively
461 if (ctstate == LockState.Read)
462 throw new LockRecursionException ();
465 static int CheckTimeout (TimeSpan timeout)
468 return checked ((int)timeout.TotalMilliseconds);
469 } catch (System.OverflowException) {
470 throw new ArgumentOutOfRangeException ("timeout");
474 static int ComputeTimeout (int millisecondsTimeout, Stopwatch sw)
476 return millisecondsTimeout == -1 ? -1 : (int)Math.Max (sw.ElapsedMilliseconds - millisecondsTimeout, 1);