2 // System.Threading.ReaderWriterLockSlim.cs
5 // Miguel de Icaza (miguel@novell.com)
6 // Dick Porter (dick@ximian.com)
7 // Jackson Harper (jackson@ximian.com)
8 // Lluis Sanchez Gual (lluis@ximian.com)
9 // Marek Safar (marek.safar@gmail.com)
11 // Copyright 2004-2008 Novell, Inc (http://www.novell.com)
12 // Copyright 2003, Ximian, Inc.
14 // NoRecursion code based on the blog post from Vance Morrison:
15 // http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
17 // Recursion code based on Mono's implementation of ReaderWriterLock.
19 // Permission is hereby granted, free of charge, to any person obtaining
20 // a copy of this software and associated documentation files (the
21 // "Software"), to deal in the Software without restriction, including
22 // without limitation the rights to use, copy, modify, merge, publish,
23 // distribute, sublicense, and/or sell copies of the Software, and to
24 // permit persons to whom the Software is furnished to do so, subject to
25 // the following conditions:
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
30 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
31 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
33 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
34 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
35 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
36 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40 using System.Collections;
41 using System.Collections.Generic;
42 using System.Security.Permissions;
43 using System.Diagnostics;
44 using System.Threading;
46 namespace System.Threading {
49 // This implementation is based on the light-weight
50 // Reader/Writer lock sample from Vance Morrison's blog:
52 // http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
54 // And in Mono's ReaderWriterLock
56 [HostProtectionAttribute(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
57 [HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
58 public class ReaderWriterLockSlim : IDisposable {
59 sealed class LockDetails
65 // Are we on a multiprocessor?
66 static readonly bool smp;
68 // Lock specifiation for myLock: This lock protects exactly the local fields associted
69 // instance of MyReaderWriterLock. It does NOT protect the memory associted with the
70 // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
73 // Who owns the lock owners > 0 => readers
74 // owners = -1 means there is one writer, Owners must be >= -1.
76 Thread upgradable_thread;
79 // These variables allow use to avoid Setting events (which is expensive) if we don't have to.
80 uint numWriteWaiters; // maximum number of threads that can be doing a WaitOne on the writeEvent
81 uint numReadWaiters; // maximum number of threads that can be doing a WaitOne on the readEvent
82 uint numUpgradeWaiters; // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1).
84 // conditions we wait on.
85 EventWaitHandle writeEvent; // threads waiting to aquire a write lock go here.
86 EventWaitHandle readEvent; // threads waiting to aquire a read lock go here (will be released in bulk)
87 EventWaitHandle upgradeEvent; // thread waiting to upgrade a read lock to a write lock go here (at most one)
91 // Only set if we are a recursive lock
92 //Dictionary<int,int> reader_locks;
94 readonly LockRecursionPolicy recursionPolicy;
95 LockDetails[] read_locks = new LockDetails [8];
97 static ReaderWriterLockSlim ()
99 smp = Environment.ProcessorCount > 1;
102 public ReaderWriterLockSlim ()
104 // NoRecursion (0) is the default value
107 public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
109 this.recursionPolicy = recursionPolicy;
111 if (recursionPolicy != LockRecursionPolicy.NoRecursion){
112 //reader_locks = new Dictionary<int,int> ();
113 throw new NotImplementedException ("recursionPolicy != NoRecursion not currently implemented");
117 public void EnterReadLock ()
119 TryEnterReadLock (-1);
122 public bool TryEnterReadLock (int millisecondsTimeout)
124 if (millisecondsTimeout < Timeout.Infinite)
125 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
127 if (read_locks == null)
128 throw new ObjectDisposedException (null);
130 if (Thread.CurrentThread == write_thread)
131 throw new LockRecursionException ("Read lock cannot be acquired while write lock is held");
135 LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, true);
136 if (ld.ReadLocks != 0) {
138 throw new LockRecursionException ("Recursive read lock can only be aquired in SupportsRecursion mode");
143 // Easy case, no contention
144 // owners >= 0 means there might be readers (but no writer)
145 if (owners >= 0 && numWriteWaiters == 0){
150 // If the request is to probe.
151 if (millisecondsTimeout == 0){
156 // We need to wait. Mark that we have waiters and wait.
157 if (readEvent == null) {
158 LazyCreateEvent (ref readEvent, false);
159 // since we left the lock, start over.
163 if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
171 public bool TryEnterReadLock (TimeSpan timeout)
173 return TryEnterReadLock (CheckTimeout (timeout));
177 // TODO: What to do if we are releasing a ReadLock and we do not own it?
179 public void ExitReadLock ()
185 throw new SynchronizationLockException ("Releasing lock and no read lock taken");
189 --GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false).ReadLocks;
191 ExitAndWakeUpAppropriateWaiters ();
194 public void EnterWriteLock ()
196 TryEnterWriteLock (-1);
199 public bool TryEnterWriteLock (int millisecondsTimeout)
201 if (millisecondsTimeout < Timeout.Infinite)
202 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
204 if (read_locks == null)
205 throw new ObjectDisposedException (null);
208 throw new LockRecursionException ();
212 LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false);
213 if (ld != null && ld.ReadLocks > 0) {
215 throw new LockRecursionException ("Write lock cannot be acquired while read lock is held");
219 // There is no contention, we are done
221 // Indicate that we have a writer
223 write_thread = Thread.CurrentThread;
227 // If we are the thread that took the Upgradable read lock
228 if (owners == 1 && upgradable_thread == Thread.CurrentThread){
230 write_thread = Thread.CurrentThread;
234 // If the request is to probe.
235 if (millisecondsTimeout == 0){
240 // We need to wait, figure out what kind of waiting.
242 if (upgradable_thread == Thread.CurrentThread){
243 // We are the upgradable thread, register our interest.
245 if (upgradeEvent == null){
246 LazyCreateEvent (ref upgradeEvent, false);
248 // since we left the lock, start over.
252 if (numUpgradeWaiters > 0){
254 throw new ApplicationException ("Upgrading lock to writer lock already in process, deadlock");
257 if (!WaitOnEvent (upgradeEvent, ref numUpgradeWaiters, millisecondsTimeout))
260 if (writeEvent == null){
261 LazyCreateEvent (ref writeEvent, true);
263 // since we left the lock, retry
266 if (!WaitOnEvent (writeEvent, ref numWriteWaiters, millisecondsTimeout))
271 Debug.Assert (owners == -1, "Owners is not -1");
276 public bool TryEnterWriteLock (TimeSpan timeout)
278 return TryEnterWriteLock (CheckTimeout (timeout));
281 public void ExitWriteLock ()
287 throw new SynchronizationLockException ("Calling ExitWriterLock when no write lock is held");
290 //Debug.Assert (numUpgradeWaiters > 0);
291 if (upgradable_thread == Thread.CurrentThread)
296 ExitAndWakeUpAppropriateWaiters ();
299 public void EnterUpgradeableReadLock ()
301 TryEnterUpgradeableReadLock (-1);
305 // Taking the Upgradable read lock is like taking a read lock
306 // but we limit it to a single upgradable at a time.
308 public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
310 if (millisecondsTimeout < Timeout.Infinite)
311 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
313 if (read_locks == null)
314 throw new ObjectDisposedException (null);
316 if (IsUpgradeableReadLockHeld)
317 throw new LockRecursionException ();
320 throw new LockRecursionException ();
324 if (owners == 0 && numWriteWaiters == 0 && upgradable_thread == null){
326 upgradable_thread = Thread.CurrentThread;
330 // If the request is to probe
331 if (millisecondsTimeout == 0){
336 if (readEvent == null){
337 LazyCreateEvent (ref readEvent, false);
338 // since we left the lock, start over.
342 if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
350 public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
352 return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
355 public void ExitUpgradeableReadLock ()
358 Debug.Assert (owners > 0, "Releasing an upgradable lock, but there was no reader!");
360 upgradable_thread = null;
361 ExitAndWakeUpAppropriateWaiters ();
364 public void Dispose ()
369 public bool IsReadLockHeld {
370 get { return RecursiveReadCount != 0; }
373 public bool IsWriteLockHeld {
374 get { return RecursiveWriteCount != 0; }
377 public bool IsUpgradeableReadLockHeld {
378 get { return RecursiveUpgradeCount != 0; }
381 public int CurrentReadCount {
382 get { return owners & 0xFFFFFFF; }
385 public int RecursiveReadCount {
388 LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false);
389 int count = ld == null ? 0 : ld.ReadLocks;
395 public int RecursiveUpgradeCount {
396 get { return upgradable_thread == Thread.CurrentThread ? 1 : 0; }
399 public int RecursiveWriteCount {
400 get { return write_thread == Thread.CurrentThread ? 1 : 0; }
403 public int WaitingReadCount {
404 get { return (int) numReadWaiters; }
407 public int WaitingUpgradeCount {
408 get { return (int) numUpgradeWaiters; }
411 public int WaitingWriteCount {
412 get { return (int) numWriteWaiters; }
415 public LockRecursionPolicy RecursionPolicy {
416 get { return recursionPolicy; }
419 #region Private methods
422 if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
426 void EnterMyLockSpin ()
429 for (int i = 0; ;i++) {
431 Thread.SpinWait (20); // Wait a few dozen instructions to let another processor release lock.
433 Thread.Sleep (0); // Give up my quantum.
435 if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
442 Debug.Assert (myLock != 0, "Exiting spin lock that is not held");
446 bool MyLockHeld { get { return myLock != 0; } }
449 /// Determines the appropriate events to set, leaves the locks, and sets the events.
451 private void ExitAndWakeUpAppropriateWaiters()
453 Debug.Assert (MyLockHeld);
455 // First a writing thread waiting on being upgraded
456 if (owners == 1 && numUpgradeWaiters != 0){
457 // Exit before signaling to improve efficiency (wakee will need the lock)
459 // release all upgraders (however there can be at most one).
462 // TODO: What does the following comment mean?
463 // two threads upgrading is a guarenteed deadlock, so we throw in that case.
464 } else if (owners == 0 && numWriteWaiters > 0) {
465 // Exit before signaling to improve efficiency (wakee will need the lock)
467 // release one writer.
470 else if (owners >= 0 && numReadWaiters != 0) {
471 // Exit before signaling to improve efficiency (wakee will need the lock)
473 // release all readers.
480 /// A routine for lazily creating a event outside the lock (so if errors
481 /// happen they are outside the lock and that we don't do much work
482 /// while holding a spin lock). If all goes well, reenter the lock and
485 void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
487 Debug.Assert (MyLockHeld);
488 Debug.Assert (waitEvent == null);
491 EventWaitHandle newEvent;
492 if (makeAutoResetEvent)
493 newEvent = new AutoResetEvent (false);
495 newEvent = new ManualResetEvent (false);
499 // maybe someone snuck in.
500 if (waitEvent == null)
501 waitEvent = newEvent;
505 /// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.
506 /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
508 bool WaitOnEvent (EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
510 Debug.Assert (MyLockHeld);
515 bool waitSuccessful = false;
517 // Do the wait outside of any lock
520 waitSuccessful = waitEvent.WaitOne (millisecondsTimeout, false);
527 return waitSuccessful;
530 static int CheckTimeout (TimeSpan timeout)
533 return checked((int) timeout.TotalMilliseconds);
534 } catch (System.OverflowException) {
535 throw new ArgumentOutOfRangeException ("timeout");
539 LockDetails GetReadLockDetails (int threadId, bool create)
543 for (i = 0; i < read_locks.Length; ++i) {
548 if (ld.ThreadId == threadId)
555 if (i == read_locks.Length)
556 Array.Resize (ref read_locks, read_locks.Length * 2);
558 ld = read_locks [i] = new LockDetails ();
559 ld.ThreadId = threadId;