Normalize line endings.
[mono.git] / mcs / class / System.Core / System.Threading / ReaderWriterLockSlim.cs
1 //
2 // System.Threading.ReaderWriterLockSlim.cs
3 //
4 // Authors:
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)
10 //
11 // Copyright 2004-2008 Novell, Inc (http://www.novell.com)
12 // Copyright 2003, Ximian, Inc.
13 //
14 // NoRecursion code based on the blog post from Vance Morrison:
15 //   http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
16 //
17 // Recursion code based on Mono's implementation of ReaderWriterLock.
18 // 
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:
26 // 
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
29 // 
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.
37 //
38
39 using System;
40 using System.Collections;
41 using System.Collections.Generic;
42 using System.Security.Permissions;
43 using System.Diagnostics;
44 using System.Threading;
45
46 namespace System.Threading {
47
48         //
49         // This implementation is based on the light-weight
50         // Reader/Writer lock sample from Vance Morrison's blog:
51         //
52         // http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
53         //
54         // And in Mono's ReaderWriterLock
55         //
56         [HostProtectionAttribute(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
57         [HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
58         public class ReaderWriterLockSlim : IDisposable {
59                 sealed class LockDetails
60                 {
61                         public int ThreadId;
62                         public int ReadLocks;
63                 }
64
65                 // Are we on a multiprocessor?
66                 static readonly bool smp;
67                 
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).
71                 int myLock;
72
73                 // Who owns the lock owners > 0 => readers
74                 // owners = -1 means there is one writer, Owners must be >= -1.  
75                 int owners;
76                 Thread upgradable_thread;
77                 Thread write_thread;
78                 
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). 
83                 
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)
88
89                 //int lock_owner;
90
91                 // Only set if we are a recursive lock
92                 //Dictionary<int,int> reader_locks;
93
94                 readonly LockRecursionPolicy recursionPolicy;
95                 LockDetails[] read_locks = new LockDetails [8];
96                 
97                 static ReaderWriterLockSlim ()
98                 {
99                         smp = Environment.ProcessorCount > 1;
100                 }
101                 
102                 public ReaderWriterLockSlim ()
103                 {
104                         // NoRecursion (0) is the default value
105                 }
106
107                 public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
108                 {
109                         this.recursionPolicy = recursionPolicy;
110                         
111                         if (recursionPolicy != LockRecursionPolicy.NoRecursion){
112                                 //reader_locks = new Dictionary<int,int> ();
113                                 throw new NotImplementedException ("recursionPolicy != NoRecursion not currently implemented");
114                         }
115                 }
116
117                 public void EnterReadLock ()
118                 {
119                         TryEnterReadLock (-1);
120                 }
121
122                 public bool TryEnterReadLock (int millisecondsTimeout)
123                 {
124                         if (millisecondsTimeout < Timeout.Infinite)
125                                 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
126                         
127                         if (read_locks == null)
128                                 throw new ObjectDisposedException (null);
129
130                         if (Thread.CurrentThread == write_thread)
131                                 throw new LockRecursionException ("Read lock cannot be acquired while write lock is held");
132
133                         EnterMyLock ();
134
135                         LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, true);
136                         if (ld.ReadLocks != 0) {
137                                 ExitMyLock ();
138                                 throw new LockRecursionException ("Recursive read lock can only be aquired in SupportsRecursion mode");
139                         }
140                         ++ld.ReadLocks;
141                         
142                         while (true){
143                                 // Easy case, no contention
144                                 // owners >= 0 means there might be readers (but no writer)
145                                 if (owners >= 0 && numWriteWaiters == 0){
146                                         owners++;
147                                         break;
148                                 }
149                                 
150                                 // If the request is to probe.
151                                 if (millisecondsTimeout == 0){
152                                         ExitMyLock ();
153                                         return false;
154                                 }
155
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. 
160                                         continue;
161                                 }
162
163                                 if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
164                                         return false;
165                         }
166                         ExitMyLock ();
167                         
168                         return true;
169                 }
170
171                 public bool TryEnterReadLock (TimeSpan timeout)
172                 {
173                         return TryEnterReadLock (CheckTimeout (timeout));
174                 }
175
176                 //
177                 // TODO: What to do if we are releasing a ReadLock and we do not own it?
178                 //
179                 public void ExitReadLock ()
180                 {
181                         EnterMyLock ();
182
183                         if (owners < 1) {
184                                 ExitMyLock ();
185                                 throw new SynchronizationLockException ("Releasing lock and no read lock taken");
186                         }
187
188                         --owners;
189                         --GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false).ReadLocks;
190
191                         ExitAndWakeUpAppropriateWaiters ();
192                 }
193
194                 public void EnterWriteLock ()
195                 {
196                         TryEnterWriteLock (-1);
197                 }
198                 
199                 public bool TryEnterWriteLock (int millisecondsTimeout)
200                 {
201                         if (millisecondsTimeout < Timeout.Infinite)
202                                 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
203
204                         if (read_locks == null)
205                                 throw new ObjectDisposedException (null);
206
207                         if (IsWriteLockHeld)
208                                 throw new LockRecursionException ();
209
210                         EnterMyLock ();
211
212                         LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false);
213                         if (ld != null && ld.ReadLocks > 0) {
214                                 ExitMyLock ();
215                                 throw new LockRecursionException ("Write lock cannot be acquired while read lock is held");
216                         }
217
218                         while (true){
219                                 // There is no contention, we are done
220                                 if (owners == 0){
221                                         // Indicate that we have a writer
222                                         owners = -1;
223                                         write_thread = Thread.CurrentThread;
224                                         break;
225                                 }
226
227                                 // If we are the thread that took the Upgradable read lock
228                                 if (owners == 1 && upgradable_thread == Thread.CurrentThread){
229                                         owners = -1;
230                                         write_thread = Thread.CurrentThread;
231                                         break;
232                                 }
233
234                                 // If the request is to probe.
235                                 if (millisecondsTimeout == 0){
236                                         ExitMyLock ();
237                                         return false;
238                                 }
239
240                                 // We need to wait, figure out what kind of waiting.
241                                 
242                                 if (upgradable_thread == Thread.CurrentThread){
243                                         // We are the upgradable thread, register our interest.
244                                         
245                                         if (upgradeEvent == null){
246                                                 LazyCreateEvent (ref upgradeEvent, false);
247
248                                                 // since we left the lock, start over.
249                                                 continue;
250                                         }
251
252                                         if (numUpgradeWaiters > 0){
253                                                 ExitMyLock ();
254                                                 throw new ApplicationException ("Upgrading lock to writer lock already in process, deadlock");
255                                         }
256                                         
257                                         if (!WaitOnEvent (upgradeEvent, ref numUpgradeWaiters, millisecondsTimeout))
258                                                 return false;
259                                 } else {
260                                         if (writeEvent == null){
261                                                 LazyCreateEvent (ref writeEvent, true);
262
263                                                 // since we left the lock, retry
264                                                 continue;
265                                         }
266                                         if (!WaitOnEvent (writeEvent, ref numWriteWaiters, millisecondsTimeout))
267                                                 return false;
268                                 }
269                         }
270
271                         Debug.Assert (owners == -1, "Owners is not -1");
272                         ExitMyLock ();
273                         return true;
274                 }
275
276                 public bool TryEnterWriteLock (TimeSpan timeout)
277                 {
278                         return TryEnterWriteLock (CheckTimeout (timeout));
279                 }
280
281                 public void ExitWriteLock ()
282                 {
283                         EnterMyLock ();
284
285                         if (owners != -1) {
286                                 ExitMyLock ();
287                                 throw new SynchronizationLockException ("Calling ExitWriterLock when no write lock is held");
288                         }
289
290                         //Debug.Assert (numUpgradeWaiters > 0);
291                         if (upgradable_thread == Thread.CurrentThread)
292                                 owners = 1;
293                         else
294                                 owners = 0;
295                         write_thread = null;
296                         ExitAndWakeUpAppropriateWaiters ();
297                 }
298
299                 public void EnterUpgradeableReadLock ()
300                 {
301                         TryEnterUpgradeableReadLock (-1);
302                 }
303
304                 //
305                 // Taking the Upgradable read lock is like taking a read lock
306                 // but we limit it to a single upgradable at a time.
307                 //
308                 public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
309                 {
310                         if (millisecondsTimeout < Timeout.Infinite)
311                                 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
312
313                         if (read_locks == null)
314                                 throw new ObjectDisposedException (null);
315
316                         if (IsUpgradeableReadLockHeld)
317                                 throw new LockRecursionException ();
318
319                         if (IsWriteLockHeld)
320                                 throw new LockRecursionException ();
321
322                         EnterMyLock ();
323                         while (true){
324                                 if (owners == 0 && numWriteWaiters == 0 && upgradable_thread == null){
325                                         owners++;
326                                         upgradable_thread = Thread.CurrentThread;
327                                         break;
328                                 }
329
330                                 // If the request is to probe
331                                 if (millisecondsTimeout == 0){
332                                         ExitMyLock ();
333                                         return false;
334                                 }
335
336                                 if (readEvent == null){
337                                         LazyCreateEvent (ref readEvent, false);
338                                         // since we left the lock, start over.
339                                         continue;
340                                 }
341
342                                 if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
343                                         return false;
344                         }
345
346                         ExitMyLock ();
347                         return true;
348                 }
349
350                 public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
351                 {
352                         return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
353                 }
354                
355                 public void ExitUpgradeableReadLock ()
356                 {
357                         EnterMyLock ();
358                         Debug.Assert (owners > 0, "Releasing an upgradable lock, but there was no reader!");
359                         --owners;
360                         upgradable_thread = null;
361                         ExitAndWakeUpAppropriateWaiters ();
362                 }
363
364                 public void Dispose ()
365                 {
366                         read_locks = null;
367                 }
368
369                 public bool IsReadLockHeld {
370                         get { return RecursiveReadCount != 0; }
371                 }
372                 
373                 public bool IsWriteLockHeld {
374                         get { return RecursiveWriteCount != 0; }
375                 }
376                 
377                 public bool IsUpgradeableReadLockHeld {
378                         get { return RecursiveUpgradeCount != 0; }
379                 }
380
381                 public int CurrentReadCount {
382                         get { return owners & 0xFFFFFFF; }
383                 }
384                 
385                 public int RecursiveReadCount {
386                         get {
387                                 EnterMyLock ();
388                                 LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false);
389                                 int count = ld == null ? 0 : ld.ReadLocks;
390                                 ExitMyLock ();
391                                 return count;
392                         }
393                 }
394
395                 public int RecursiveUpgradeCount {
396                         get { return upgradable_thread == Thread.CurrentThread ? 1 : 0; }
397                 }
398
399                 public int RecursiveWriteCount {
400                         get { return write_thread == Thread.CurrentThread ? 1 : 0; }
401                 }
402
403                 public int WaitingReadCount {
404                         get { return (int) numReadWaiters; }
405                 }
406
407                 public int WaitingUpgradeCount {
408                         get { return (int) numUpgradeWaiters; }
409                 }
410
411                 public int WaitingWriteCount {
412                         get { return (int) numWriteWaiters; }
413                 }
414
415                 public LockRecursionPolicy RecursionPolicy {
416                         get { return recursionPolicy; }
417                 }
418                 
419 #region Private methods
420                 void EnterMyLock ()
421                 {
422                         if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
423                                 EnterMyLockSpin ();
424                 }
425
426                 void EnterMyLockSpin ()
427                 {
428
429                         for (int i = 0; ;i++) {
430                                 if (i < 3 && smp)
431                                         Thread.SpinWait (20);    // Wait a few dozen instructions to let another processor release lock. 
432                                 else 
433                                         Thread.Sleep (0);        // Give up my quantum.  
434
435                                 if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
436                                         return;
437                         }
438                 }
439
440                 void ExitMyLock()
441                 {
442                         Debug.Assert (myLock != 0, "Exiting spin lock that is not held");
443                         myLock = 0;
444                 }
445
446                 bool MyLockHeld { get { return myLock != 0; } }
447
448                 /// <summary>
449                 /// Determines the appropriate events to set, leaves the locks, and sets the events. 
450                 /// </summary>
451                 private void ExitAndWakeUpAppropriateWaiters()
452                 {
453                         Debug.Assert (MyLockHeld);
454
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)
458                                 ExitMyLock ();
459                                 // release all upgraders (however there can be at most one). 
460                                 upgradeEvent.Set ();
461                                 //
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)
466                                 ExitMyLock ();
467                                 // release one writer. 
468                                 writeEvent.Set ();
469                         }
470                         else if (owners >= 0 && numReadWaiters != 0) {
471                                 // Exit before signaling to improve efficiency (wakee will need the lock)
472                                 ExitMyLock ();
473                                 // release all readers.
474                                 readEvent.Set();
475                         } else
476                                 ExitMyLock();
477                 }
478
479                 /// <summary>
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
483                 /// set 'waitEvent' 
484                 /// </summary>
485                 void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
486                 {
487                         Debug.Assert (MyLockHeld);
488                         Debug.Assert (waitEvent == null);
489                         
490                         ExitMyLock ();
491                         EventWaitHandle newEvent;
492                         if (makeAutoResetEvent) 
493                                 newEvent = new AutoResetEvent (false);
494                         else 
495                                 newEvent = new ManualResetEvent (false);
496
497                         EnterMyLock ();
498
499                         // maybe someone snuck in. 
500                         if (waitEvent == null)
501                                 waitEvent = newEvent;
502                 }
503
504                 /// <summary>
505                 /// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.  
506                 /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
507                 /// </summary>
508                 bool WaitOnEvent (EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
509                 {
510                         Debug.Assert (MyLockHeld);
511
512                         waitEvent.Reset ();
513                         numWaiters++;
514
515                         bool waitSuccessful = false;
516
517                         // Do the wait outside of any lock 
518                         ExitMyLock();      
519                         try {
520                                 waitSuccessful = waitEvent.WaitOne (millisecondsTimeout, false);
521                         } finally {
522                                 EnterMyLock ();
523                                 --numWaiters;
524                                 if (!waitSuccessful)
525                                         ExitMyLock ();
526                         }
527                         return waitSuccessful;
528                 }
529                 
530                 static int CheckTimeout (TimeSpan timeout)
531                 {
532                         try {
533                                 return checked((int) timeout.TotalMilliseconds);
534                         } catch (System.OverflowException) {
535                                 throw new ArgumentOutOfRangeException ("timeout");                              
536                         }
537                 }
538
539                 LockDetails GetReadLockDetails (int threadId, bool create)
540                 {
541                         int i;
542                         LockDetails ld;
543                         for (i = 0; i < read_locks.Length; ++i) {
544                                 ld = read_locks [i];
545                                 if (ld == null)
546                                         break;
547
548                                 if (ld.ThreadId == threadId)
549                                         return ld;
550                         }
551
552                         if (!create)
553                                 return null;
554
555                         if (i == read_locks.Length)
556                                 Array.Resize (ref read_locks, read_locks.Length * 2);
557                                 
558                         ld = read_locks [i] = new LockDetails ();
559                         ld.ThreadId = threadId;
560                         return ld;
561                 }
562 #endregion
563         }
564 }