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;
48 const int RwWaitUpgrade = 2;
49 const int RwWrite = 4;
54 readonly LockRecursionPolicy recursionPolicy;
56 AtomicBoolean upgradableTaken = new AtomicBoolean ();
58 ManualResetEventSlim upgradableEvent = new ManualResetEventSlim (true);
59 ManualResetEventSlim writerDoneEvent = new ManualResetEventSlim (true);
60 ManualResetEventSlim readerDoneEvent = new ManualResetEventSlim (true);
62 ManualResetEvent upgradableEvent = new ManualResetEvent (true);
63 ManualResetEvent writerDoneEvent = new ManualResetEvent (true);
64 ManualResetEvent readerDoneEvent = new ManualResetEvent (true);
67 int numReadWaiters, numUpgradeWaiters, numWriteWaiters;
70 static int idPool = int.MinValue;
71 readonly int id = Interlocked.Increment (ref idPool);
74 static IDictionary<int, ThreadLockState> currentThreadState;
76 public ReaderWriterLockSlim () : this (LockRecursionPolicy.NoRecursion)
80 public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
82 this.recursionPolicy = recursionPolicy;
85 public void EnterReadLock ()
87 TryEnterReadLock (-1);
90 public bool TryEnterReadLock (int millisecondsTimeout)
92 ThreadLockState ctstate = CurrentThreadState;
94 if (CheckState (millisecondsTimeout, LockState.Read)) {
95 ctstate.ReaderRecursiveCount++;
99 // This is downgrading from upgradable, no need for check since
100 // we already have a sort-of read lock that's going to disappear
101 // after user calls ExitUpgradeableReadLock.
102 // Same idea when recursion is allowed and a write thread wants to
103 // go for a Read too.
104 if (CurrentLockState.Has (LockState.Upgradable)
105 || recursionPolicy == LockRecursionPolicy.SupportsRecursion) {
106 Interlocked.Add (ref rwlock, RwRead);
107 ctstate.LockState ^= LockState.Read;
108 ctstate.ReaderRecursiveCount++;
113 Stopwatch sw = Stopwatch.StartNew ();
114 Interlocked.Increment (ref numReadWaiters);
116 while (millisecondsTimeout == -1 || sw.ElapsedMilliseconds < millisecondsTimeout) {
117 if ((rwlock & 0x7) > 0) {
118 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
122 if ((Interlocked.Add (ref rwlock, RwRead) & 0x7) == 0) {
123 ctstate.LockState ^= LockState.Read;
124 ctstate.ReaderRecursiveCount++;
125 Interlocked.Decrement (ref numReadWaiters);
126 if (readerDoneEvent.IsSet ())
127 readerDoneEvent.Reset ();
131 Interlocked.Add (ref rwlock, -RwRead);
133 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
136 Interlocked.Decrement (ref numReadWaiters);
140 public bool TryEnterReadLock (TimeSpan timeout)
142 return TryEnterReadLock (CheckTimeout (timeout));
145 public void ExitReadLock ()
147 ThreadLockState ctstate = CurrentThreadState;
149 if (!ctstate.LockState.Has (LockState.Read))
150 throw new SynchronizationLockException ("The current thread has not entered the lock in read mode");
152 ctstate.LockState ^= LockState.Read;
153 ctstate.ReaderRecursiveCount--;
154 if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
155 readerDoneEvent.Set ();
158 public void EnterWriteLock ()
160 TryEnterWriteLock (-1);
163 public bool TryEnterWriteLock (int millisecondsTimeout)
165 ThreadLockState ctstate = CurrentThreadState;
167 if (CheckState (millisecondsTimeout, LockState.Write)) {
168 ctstate.WriterRecursiveCount++;
172 Stopwatch sw = Stopwatch.StartNew ();
173 Interlocked.Increment (ref numWriteWaiters);
174 bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
176 // If the code goes there that means we had a read lock beforehand
177 if (isUpgradable && rwlock >= RwRead)
178 Interlocked.Add (ref rwlock, -RwRead);
180 int stateCheck = isUpgradable ? RwWaitUpgrade : RwWait;
181 int appendValue = RwWait | (isUpgradable ? RwWaitUpgrade : 0);
183 while (millisecondsTimeout < 0 || sw.ElapsedMilliseconds < millisecondsTimeout) {
186 if (state <= stateCheck) {
187 if (Interlocked.CompareExchange (ref rwlock, RwWrite, state) == state) {
188 ctstate.LockState ^= LockState.Write;
189 ctstate.WriterRecursiveCount++;
190 Interlocked.Decrement (ref numWriteWaiters);
191 if (writerDoneEvent.IsSet ())
192 writerDoneEvent.Reset ();
198 while ((state & RwWait) == 0 && Interlocked.CompareExchange (ref rwlock, state | appendValue, state) == state)
201 while (rwlock > stateCheck && (millisecondsTimeout < 0 || sw.ElapsedMilliseconds < millisecondsTimeout))
202 readerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
205 Interlocked.Decrement (ref numWriteWaiters);
209 public bool TryEnterWriteLock (TimeSpan timeout)
211 return TryEnterWriteLock (CheckTimeout (timeout));
214 public void ExitWriteLock ()
216 ThreadLockState ctstate = CurrentThreadState;
218 if (!ctstate.LockState.Has (LockState.Write))
219 throw new SynchronizationLockException ("The current thread has not entered the lock in write mode");
221 ctstate.LockState ^= LockState.Write;
222 ctstate.WriterRecursiveCount--;
223 Interlocked.Add (ref rwlock, -RwWrite);
224 writerDoneEvent.Set ();
227 public void EnterUpgradeableReadLock ()
229 TryEnterUpgradeableReadLock (-1);
233 // Taking the Upgradable read lock is like taking a read lock
234 // but we limit it to a single upgradable at a time.
236 public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
238 ThreadLockState ctstate = CurrentThreadState;
240 if (CheckState (millisecondsTimeout, LockState.Upgradable)) {
241 ctstate.UpgradeableRecursiveCount++;
245 if (ctstate.LockState.Has (LockState.Read))
246 throw new LockRecursionException ("The current thread has already entered read mode");
248 Stopwatch sw = Stopwatch.StartNew ();
249 Interlocked.Increment (ref numUpgradeWaiters);
251 while (!upgradableEvent.IsSet () || !upgradableTaken.TryRelaxedSet ()) {
252 if (millisecondsTimeout != -1 && sw.ElapsedMilliseconds > millisecondsTimeout) {
253 Interlocked.Decrement (ref numUpgradeWaiters);
257 upgradableEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
260 upgradableEvent.Reset ();
262 if (TryEnterReadLock (ComputeTimeout (millisecondsTimeout, sw))) {
263 ctstate.LockState = LockState.Upgradable;
264 Interlocked.Decrement (ref numUpgradeWaiters);
265 ctstate.ReaderRecursiveCount--;
266 ctstate.UpgradeableRecursiveCount++;
270 upgradableTaken.Value = false;
271 upgradableEvent.Set ();
273 Interlocked.Decrement (ref numUpgradeWaiters);
278 public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
280 return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
283 public void ExitUpgradeableReadLock ()
285 ThreadLockState ctstate = CurrentThreadState;
287 if (!ctstate.LockState.Has (LockState.Upgradable | LockState.Read))
288 throw new SynchronizationLockException ("The current thread has not entered the lock in upgradable mode");
290 upgradableTaken.Value = false;
291 upgradableEvent.Set ();
293 ctstate.LockState ^= LockState.Upgradable;
294 ctstate.UpgradeableRecursiveCount--;
295 Interlocked.Add (ref rwlock, -RwRead);
298 public void Dispose ()
303 public bool IsReadLockHeld {
305 return rwlock >= RwRead;
309 public bool IsWriteLockHeld {
311 return (rwlock & RwWrite) > 0;
315 public bool IsUpgradeableReadLockHeld {
317 return upgradableTaken.Value;
321 public int CurrentReadCount {
323 return (rwlock >> RwReadBit) - (IsUpgradeableReadLockHeld ? 1 : 0);
327 public int RecursiveReadCount {
329 return CurrentThreadState.ReaderRecursiveCount;
333 public int RecursiveUpgradeCount {
335 return CurrentThreadState.UpgradeableRecursiveCount;
339 public int RecursiveWriteCount {
341 return CurrentThreadState.WriterRecursiveCount;
345 public int WaitingReadCount {
347 return numReadWaiters;
351 public int WaitingUpgradeCount {
353 return numUpgradeWaiters;
357 public int WaitingWriteCount {
359 return numWriteWaiters;
363 public LockRecursionPolicy RecursionPolicy {
365 return recursionPolicy;
369 LockState CurrentLockState {
371 return CurrentThreadState.LockState;
374 CurrentThreadState.LockState = value;
378 ThreadLockState CurrentThreadState {
380 if (currentThreadState == null)
381 currentThreadState = new Dictionary<int, ThreadLockState> ();
383 ThreadLockState state;
384 if (!currentThreadState.TryGetValue (id, out state))
385 currentThreadState[id] = state = new ThreadLockState ();
391 bool CheckState (int millisecondsTimeout, LockState validState)
394 throw new ObjectDisposedException ("ReaderWriterLockSlim");
396 if (millisecondsTimeout < Timeout.Infinite)
397 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
399 // Detect and prevent recursion
400 LockState ctstate = CurrentLockState;
402 if (recursionPolicy == LockRecursionPolicy.NoRecursion)
403 if ((ctstate != LockState.None && ctstate != LockState.Upgradable)
404 || (ctstate == LockState.Upgradable && validState == LockState.Upgradable))
405 throw new LockRecursionException ("The current thread has already a lock and recursion isn't supported");
407 // If we already had right lock state, just return
408 if (ctstate.Has (validState))
411 CheckRecursionAuthorization (ctstate, validState);
416 static void CheckRecursionAuthorization (LockState ctstate, LockState desiredState)
418 // In read mode you can just enter Read recursively
419 if (ctstate == LockState.Read)
420 throw new LockRecursionException ();
423 static int CheckTimeout (TimeSpan timeout)
426 return checked ((int)timeout.TotalMilliseconds);
427 } catch (System.OverflowException) {
428 throw new ArgumentOutOfRangeException ("timeout");
432 static int ComputeTimeout (int millisecondsTimeout, Stopwatch sw)
434 return millisecondsTimeout == -1 ? -1 : (int)Math.Max (sw.ElapsedMilliseconds - millisecondsTimeout, 1);