ReaderWriterLockSlim.cs now builds with mono.
[mono.git] / mcs / class / referencesource / System.Core / System / threading / ReaderWriterLockSlim / ReaderWriterLockSlim.cs
1 using System;                            // for Basic system types
2 using System.IO;                         // for File, Path 
3 using System.Diagnostics;                // for TraceInformation ...
4 using System.Threading;
5 using System.Security.Permissions;
6 using System.Runtime.CompilerServices;
7 #if FEATURE_NETCORE
8 using System.Core;  // for strongly typed resources
9 #endif
10
11 namespace System.Threading
12 {
13     public enum LockRecursionPolicy
14     {
15         NoRecursion = 0,
16         SupportsRecursion = 1,
17     }
18
19     //
20     // ReaderWriterCount tracks how many of each kind of lock is held by each thread.
21     // We keep a linked list for each thread, attached to a ThreadStatic field.
22     // These are reused wherever possible, so that a given thread will only
23     // allocate N of these, where N is the maximum number of locks held simultaneously
24     // by that thread.
25     // 
26     internal class ReaderWriterCount
27     {
28         // Which lock does this object belong to?  This is a numeric ID for two reasons:
29         // 1) We don't want this field to keep the lock object alive, and a WeakReference would
30         //    be too expensive.
31         // 2) Setting the value of a long is faster than setting the value of a reference.
32         //    The "hot" paths in ReaderWriterLockSlim are short enough that this actually
33         //    matters.
34         public long lockID;
35
36         // How many reader locks does this thread hold on this ReaderWriterLockSlim instance?
37         public int readercount;
38
39         // Ditto for writer/upgrader counts.  These are only used if the lock allows recursion.
40         // But we have to have the fields on every ReaderWriterCount instance, because 
41         // we reuse it for different locks.
42         public int writercount;
43         public int upgradecount;
44
45         // Next RWC in this thread's list.
46         public ReaderWriterCount next;
47     }
48
49     /// <summary>
50     /// A reader-writer lock implementation that is intended to be simple, yet very
51     /// efficient.  In particular only 1 interlocked operation is taken for any lock 
52     /// operation (we use spin locks to achieve this).  The spin lock is never held
53     /// for more than a few instructions (in particular, we never call event APIs
54     /// or in fact any non-trivial API while holding the spin lock).   
55     /// </summary>
56 #if !FEATURE_NETCORE
57     [HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)]
58 #endif
59     [HostProtection(MayLeakOnAbort = true)]
60     public class ReaderWriterLockSlim : IDisposable
61     {
62         //Specifying if the lock can be reacquired recursively.
63         bool fIsReentrant;
64
65         // Lock specification for myLock:  This lock protects exactly the local fields associated
66         // instance of ReaderWriterLockSlim.  It does NOT protect the memory associated with the
67         // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
68         int myLock;
69
70         //The variables controlling spinning behavior of Mylock(which is a spin-lock)
71
72         const int LockSpinCycles = 20;
73         const int LockSpinCount = 10;
74         const int LockSleep0Count = 5;
75
76         // These variables allow use to avoid Setting events (which is expensive) if we don't have to. 
77         uint numWriteWaiters;        // maximum number of threads that can be doing a WaitOne on the writeEvent 
78         uint numReadWaiters;         // maximum number of threads that can be doing a WaitOne on the readEvent
79         uint numWriteUpgradeWaiters;      // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1). 
80         uint numUpgradeWaiters;
81
82         //Variable used for quick check when there are no waiters.
83         bool fNoWaiters;
84
85         int upgradeLockOwnerId;
86         int writeLockOwnerId;
87
88         // conditions we wait on. 
89         EventWaitHandle writeEvent;    // threads waiting to acquire a write lock go here.
90         EventWaitHandle readEvent;     // threads waiting to acquire a read lock go here (will be released in bulk)
91         EventWaitHandle upgradeEvent;  // thread waiting to acquire the upgrade lock
92         EventWaitHandle waitUpgradeEvent;  // thread waiting to upgrade from the upgrade lock to a write lock go here (at most one)
93
94         // Every lock instance has a unique ID, which is used by ReaderWriterCount to associate itself with the lock
95         // without holding a reference to it.
96         static long s_nextLockID;
97         long lockID;
98
99         // See comments on ReaderWriterCount.
100                 [ThreadStatic]
101         static ReaderWriterCount t_rwc;
102
103         bool fUpgradeThreadHoldingRead;
104
105         private const int MaxSpinCount = 20;
106
107         //The uint, that contains info like if the writer lock is held, num of 
108         //readers etc.
109         uint owners;
110
111         //Various R/W masks
112         //Note:
113         //The Uint is divided as follows:
114         //
115         //Writer-Owned  Waiting-Writers   Waiting Upgraders     Num-Readers
116         //    31          30                 29                 28.......0
117         //
118         //Dividing the uint, allows to vastly simplify logic for checking if a 
119         //reader should go in etc. Setting the writer bit, will automatically
120         //make the value of the uint much larger than the max num of readers 
121         //allowed, thus causing the check for max_readers to fail. 
122
123         private const uint WRITER_HELD = 0x80000000;
124         private const uint WAITING_WRITERS = 0x40000000;
125         private const uint WAITING_UPGRADER = 0x20000000;
126
127         //The max readers is actually one less than it's theoretical max.
128         //This is done in order to prevent reader count overflows. If the reader
129         //count reaches max, other readers will wait.
130         private const uint MAX_READER = 0x10000000 - 2;
131
132         private const uint READER_MASK = 0x10000000 - 1;
133
134         private bool fDisposed;
135
136         private void InitializeThreadCounts()
137         {
138             upgradeLockOwnerId = -1;
139             writeLockOwnerId = -1;
140         }
141
142         public ReaderWriterLockSlim()
143             : this(LockRecursionPolicy.NoRecursion)                   
144         {
145         }
146
147         public ReaderWriterLockSlim(LockRecursionPolicy recursionPolicy)            
148         {
149             if (recursionPolicy == LockRecursionPolicy.SupportsRecursion)
150             {
151                 fIsReentrant = true;
152             }            
153             InitializeThreadCounts();
154             fNoWaiters = true;
155             lockID = Interlocked.Increment(ref s_nextLockID);
156         }
157
158 #if NET_4_5
159         [MethodImpl(MethodImplOptions.AggressiveInlining)]
160 #endif
161         private static bool IsRWEntryEmpty(ReaderWriterCount rwc)
162         {
163             if (rwc.lockID == 0)
164                 return true;
165             else if (rwc.readercount == 0 && rwc.writercount == 0 && rwc.upgradecount == 0)
166                 return true;
167             else
168                 return false;
169         }
170
171         private bool IsRwHashEntryChanged(ReaderWriterCount lrwc)
172         {
173             return lrwc.lockID != this.lockID;
174         }
175
176         /// <summary>
177         /// This routine retrieves/sets the per-thread counts needed to enforce the
178         /// various rules related to acquiring the lock. 
179         /// 
180         /// DontAllocate is set to true if the caller just wants to get an existing
181         /// entry for this thread, but doesn't want to add one if an existing one
182         /// could not be found.
183         /// </summary>
184 #if NET_4_5
185                 [MethodImpl(MethodImplOptions.AggressiveInlining)]
186 #endif
187         private ReaderWriterCount GetThreadRWCount(bool dontAllocate)
188         {
189                         ReaderWriterCount rwc = t_rwc;
190                         ReaderWriterCount empty = null;
191                         while (rwc != null)
192                         {
193                                 if (rwc.lockID == this.lockID)
194                                         return rwc;
195                                         
196                                 if (!dontAllocate && empty == null && IsRWEntryEmpty(rwc))
197                                         empty = rwc;
198
199                 rwc = rwc.next;
200                         }
201                         
202                         if (dontAllocate)
203                                 return null;
204
205             if (empty == null)
206             {
207                 empty = new ReaderWriterCount();
208                 empty.next = t_rwc;
209                 t_rwc = empty;
210             }
211
212             empty.lockID = this.lockID;
213             return empty;
214         }
215
216         public void EnterReadLock()
217         {
218             TryEnterReadLock(-1);
219         }
220
221         //
222         // Common timeout support
223         //
224         private struct TimeoutTracker
225         {
226             private int m_total;
227             private int m_start;
228
229             public TimeoutTracker(TimeSpan timeout)
230             {
231                 long ltm = (long)timeout.TotalMilliseconds;
232                 if (ltm < -1 || ltm > (long)Int32.MaxValue)
233                     throw new ArgumentOutOfRangeException("timeout");
234                 m_total = (int)ltm;
235                 if (m_total != -1 && m_total != 0)
236                     m_start = Environment.TickCount;
237                 else
238                     m_start = 0;
239             }
240
241             public TimeoutTracker(int millisecondsTimeout)
242             {
243                 if (millisecondsTimeout < -1)
244                     throw new ArgumentOutOfRangeException("millisecondsTimeout");
245                 m_total = millisecondsTimeout;
246                 if (m_total != -1 && m_total != 0)
247                     m_start = Environment.TickCount;
248                 else
249                     m_start = 0;
250             }
251
252             public int RemainingMilliseconds
253             {
254                 get
255                 {
256                     if (m_total == -1 || m_total == 0)
257                         return m_total;
258
259                     int elapsed = Environment.TickCount - m_start;
260                     // elapsed may be negative if TickCount has overflowed by 2^31 milliseconds.
261                     if (elapsed < 0 || elapsed >= m_total)
262                         return 0;
263
264                     return m_total - elapsed;
265                 }
266             }
267
268             public bool IsExpired
269             {
270                 get
271                 {
272                     return RemainingMilliseconds == 0;
273                 }
274             }
275         }
276
277         public bool TryEnterReadLock(TimeSpan timeout)
278         {
279             return TryEnterReadLock(new TimeoutTracker(timeout));
280         }
281
282         public bool TryEnterReadLock(int millisecondsTimeout)
283         {
284             return TryEnterReadLock(new TimeoutTracker(millisecondsTimeout));
285         }
286
287         private bool TryEnterReadLock(TimeoutTracker timeout)
288         {
289 #if !FEATURE_NETCORE
290             Thread.BeginCriticalRegion();
291 #endif // !FEATURE_NETCORE
292             bool result = false;
293             try
294             {
295                 result = TryEnterReadLockCore(timeout);
296             }
297             finally
298             {
299 #if !FEATURE_NETCORE
300                 if (!result)
301                     Thread.EndCriticalRegion();
302 #endif // !FEATURE_NETCORE
303             }
304             return result;
305         }
306
307         private bool TryEnterReadLockCore(TimeoutTracker timeout)
308         {
309             if(fDisposed)
310                 throw new ObjectDisposedException(null);
311
312             ReaderWriterCount lrwc = null;
313             int id = Thread.CurrentThread.ManagedThreadId;
314
315             if (!fIsReentrant)
316             {
317
318                 if (id == writeLockOwnerId)
319                 {
320                     //Check for AW->AR
321                     throw new LockRecursionException(SR.GetString(SR.LockRecursionException_ReadAfterWriteNotAllowed));
322                 }
323
324                 EnterMyLock();
325
326                 lrwc = GetThreadRWCount(false);
327
328                 //Check if the reader lock is already acquired. Note, we could
329                 //check the presence of a reader by not allocating rwc (But that 
330                 //would lead to two lookups in the common case. It's better to keep
331                 //a count in the struucture).
332                 if (lrwc.readercount > 0)
333                 {
334                     ExitMyLock();
335                     throw new LockRecursionException(SR.GetString(SR.LockRecursionException_RecursiveReadNotAllowed));
336                 }
337                 else if (id == upgradeLockOwnerId)
338                 {
339                     //The upgrade lock is already held.
340                     //Update the global read counts and exit.
341
342                     lrwc.readercount++;
343                     owners++;
344                     ExitMyLock();
345                     return true;
346                 }
347             }
348             else
349             {
350                 EnterMyLock();
351                 lrwc = GetThreadRWCount(false);
352                 if (lrwc.readercount > 0)
353                 {
354                     lrwc.readercount++;
355                     ExitMyLock();
356                     return true;
357                 }
358                 else if (id == upgradeLockOwnerId)
359                 {
360                     //The upgrade lock is already held.
361                     //Update the global read counts and exit.
362                     lrwc.readercount++;
363                     owners++;
364                     ExitMyLock();
365                     fUpgradeThreadHoldingRead = true;
366                     return true;
367                 }
368                 else if (id == writeLockOwnerId)
369                 {
370                     //The write lock is already held.
371                     //Update global read counts here,
372                     lrwc.readercount++;
373                     owners++;
374                     ExitMyLock();
375                     return true;
376                 }
377             }
378
379             bool retVal = true;
380
381             int spincount = 0;
382
383             for (; ; )
384             {
385                 // We can enter a read lock if there are only read-locks have been given out
386                 // and a writer is not trying to get in.  
387
388                 if (owners < MAX_READER)
389                 {
390                     // Good case, there is no contention, we are basically done
391                     owners++;       // Indicate we have another reader
392                     lrwc.readercount++;
393                     break;
394                 }
395
396                 if (spincount < MaxSpinCount)
397                 {
398                     ExitMyLock();
399                     if (timeout.IsExpired)
400                         return false;
401                     spincount++;
402                     SpinWait(spincount);
403                     EnterMyLock();
404                     //The per-thread structure may have been recycled as the lock is acquired (due to message pumping), load again.
405                     if(IsRwHashEntryChanged(lrwc))
406                         lrwc = GetThreadRWCount(false);
407                     continue;
408                 }
409
410                 // Drat, we need to wait.  Mark that we have waiters and wait.  
411                 if (readEvent == null)      // Create the needed event 
412                 {
413                     LazyCreateEvent(ref readEvent, false);
414                     if (IsRwHashEntryChanged(lrwc))
415                         lrwc = GetThreadRWCount(false);
416                     continue;   // since we left the lock, start over. 
417                 }
418
419                 retVal = WaitOnEvent(readEvent, ref numReadWaiters, timeout);
420                 if (!retVal)
421                 {
422                     return false;
423                 }
424                 if (IsRwHashEntryChanged(lrwc))
425                     lrwc = GetThreadRWCount(false);
426             }
427
428             ExitMyLock();
429             return retVal;
430         }
431
432         public void EnterWriteLock()
433         {
434             TryEnterWriteLock(-1);
435         }
436
437         public bool TryEnterWriteLock(TimeSpan timeout)
438         {
439             return TryEnterWriteLock(new TimeoutTracker(timeout));
440         }
441
442         public bool TryEnterWriteLock(int millisecondsTimeout)
443         {
444             return TryEnterWriteLock(new TimeoutTracker(millisecondsTimeout));
445         }
446
447         private bool TryEnterWriteLock(TimeoutTracker timeout)
448         {
449 #if !FEATURE_NETCORE
450             Thread.BeginCriticalRegion();
451 #endif // !FEATURE_NETCORE
452
453             bool result = false;
454             try
455             {
456                 result = TryEnterWriteLockCore(timeout);
457             }
458             finally
459             {
460 #if !FEATURE_NETCORE
461                 if (!result) 
462                     Thread.EndCriticalRegion();
463 #endif // !FEATURE_NETCORE
464             }
465             return result;
466         }
467
468         private bool TryEnterWriteLockCore(TimeoutTracker timeout)
469         {
470             if(fDisposed)
471                 throw new ObjectDisposedException(null); 
472
473             int id = Thread.CurrentThread.ManagedThreadId;
474             ReaderWriterCount lrwc;
475             bool upgradingToWrite = false;
476
477             if (!fIsReentrant)
478             {
479                 if (id == writeLockOwnerId)
480                 {
481                     //Check for AW->AW
482                     throw new LockRecursionException(SR.GetString(SR.LockRecursionException_RecursiveWriteNotAllowed));
483                 }
484                 else if (id == upgradeLockOwnerId)
485                 {
486                     //AU->AW case is allowed once.
487                     upgradingToWrite = true;
488                 }
489
490                 EnterMyLock();
491                 lrwc = GetThreadRWCount(true);
492
493                 //Can't acquire write lock with reader lock held. 
494                 if (lrwc != null && lrwc.readercount > 0)
495                 {
496                     ExitMyLock();
497                     throw new LockRecursionException(SR.GetString(SR.LockRecursionException_WriteAfterReadNotAllowed));
498                 }
499             }
500             else
501             {
502                 EnterMyLock();
503                 lrwc = GetThreadRWCount(false);
504
505                 if (id == writeLockOwnerId)
506                 {
507                     lrwc.writercount++;
508                     ExitMyLock();
509                     return true;
510                 }
511                 else if (id == upgradeLockOwnerId)
512                 {
513                     upgradingToWrite = true;
514                 }
515                 else if (lrwc.readercount > 0)
516                 {
517                     //Write locks may not be acquired if only read locks have been
518                     //acquired.
519                     ExitMyLock();
520                     throw new LockRecursionException(SR.GetString(SR.LockRecursionException_WriteAfterReadNotAllowed));
521                 }
522             }
523
524             int spincount = 0;
525             bool retVal = true;
526
527             for (; ; )
528             {
529                 if (IsWriterAcquired())
530                 {
531                     // Good case, there is no contention, we are basically done
532                     SetWriterAcquired();
533                     break;
534                 }
535
536                 //Check if there is just one upgrader, and no readers.
537                 //Assumption: Only one thread can have the upgrade lock, so the 
538                 //following check will fail for all other threads that may sneak in 
539                 //when the upgrading thread is waiting.
540
541                 if (upgradingToWrite)
542                 {
543                     uint readercount = GetNumReaders();
544
545                     if (readercount == 1)
546                     {
547                         //Good case again, there is just one upgrader, and no readers.
548                         SetWriterAcquired();    // indicate we have a writer.
549                         break;
550                     }
551                     else if (readercount == 2)
552                     {
553                         if (lrwc != null)
554                         {
555                             if (IsRwHashEntryChanged(lrwc))
556                                 lrwc = GetThreadRWCount(false);
557
558                             if (lrwc.readercount > 0)
559                             {
560                                 //This check is needed for EU->ER->EW case, as the owner count will be two.
561                                 Debug.Assert(fIsReentrant);
562                                 Debug.Assert(fUpgradeThreadHoldingRead);
563
564                                 //Good case again, there is just one upgrader, and no readers.
565                                 SetWriterAcquired();   // indicate we have a writer.
566                                 break;
567                             }                            
568                         }
569                     }
570                 }
571
572                 if (spincount < MaxSpinCount)
573                 {
574                     ExitMyLock();
575                     if (timeout.IsExpired)
576                         return false;
577                     spincount++;
578                     SpinWait(spincount);
579                     EnterMyLock();
580                     continue;
581                 }
582
583                 if (upgradingToWrite)
584                 {
585                     if (waitUpgradeEvent == null)   // Create the needed event
586                     {
587                         LazyCreateEvent(ref waitUpgradeEvent, true);
588                         continue;   // since we left the lock, start over. 
589                     }
590
591                     Debug.Assert(numWriteUpgradeWaiters == 0, "There can be at most one thread with the upgrade lock held.");
592
593                     retVal = WaitOnEvent(waitUpgradeEvent, ref numWriteUpgradeWaiters, timeout);
594
595                     //The lock is not held in case of failure.
596                     if (!retVal)
597                         return false;
598                 }
599                 else
600                 {
601                     // Drat, we need to wait.  Mark that we have waiters and wait.
602                     if (writeEvent == null)     // create the needed event.
603                     {
604                         LazyCreateEvent(ref writeEvent, true);
605                         continue;   // since we left the lock, start over. 
606                     }
607
608                     retVal = WaitOnEvent(writeEvent, ref numWriteWaiters, timeout);
609                     //The lock is not held in case of failure.
610                     if (!retVal)
611                         return false;
612                 }
613             }
614
615             Debug.Assert((owners & WRITER_HELD) > 0);
616
617             if (fIsReentrant)
618             {
619                 if (IsRwHashEntryChanged(lrwc))
620                     lrwc = GetThreadRWCount(false);
621                 lrwc.writercount++;
622             }
623
624             ExitMyLock();
625
626             writeLockOwnerId = id;
627
628             return true;
629         }
630
631         public void EnterUpgradeableReadLock()
632         {
633             TryEnterUpgradeableReadLock(-1);
634         }
635
636         public bool TryEnterUpgradeableReadLock(TimeSpan timeout)
637         {
638             return TryEnterUpgradeableReadLock(new TimeoutTracker(timeout));
639         }
640
641         public bool TryEnterUpgradeableReadLock(int millisecondsTimeout)
642         {
643             return TryEnterUpgradeableReadLock(new TimeoutTracker(millisecondsTimeout));
644         }
645
646         private bool TryEnterUpgradeableReadLock(TimeoutTracker timeout)
647         {
648 #if !FEATURE_NETCORE
649             Thread.BeginCriticalRegion();
650 #endif // !FEATURE_NETCORE
651             bool result = false;
652             try
653             {
654                 result = TryEnterUpgradeableReadLockCore(timeout);
655             }
656             finally
657             {
658 #if !FEATURE_NETCORE
659                 if (!result)
660                     Thread.EndCriticalRegion();
661 #endif // !FEATURE_NETCORE
662             }
663             return result;
664         }
665
666         private bool TryEnterUpgradeableReadLockCore(TimeoutTracker timeout)
667         {
668             if(fDisposed)
669                 throw new ObjectDisposedException(null); 
670
671             int id = Thread.CurrentThread.ManagedThreadId;
672             ReaderWriterCount lrwc;
673
674             if (!fIsReentrant)
675             {
676                 if (id == upgradeLockOwnerId)
677                 {
678                     //Check for AU->AU
679                     throw new LockRecursionException(SR.GetString(SR.LockRecursionException_RecursiveUpgradeNotAllowed));
680                 }
681                 else if (id == writeLockOwnerId)
682                 {
683                     //Check for AU->AW
684                     throw new LockRecursionException(SR.GetString(SR.LockRecursionException_UpgradeAfterWriteNotAllowed));
685                 }
686
687                 EnterMyLock();
688                 lrwc = GetThreadRWCount(true);
689                 //Can't acquire upgrade lock with reader lock held. 
690                 if (lrwc != null && lrwc.readercount > 0)
691                 {
692                     ExitMyLock();
693                     throw new LockRecursionException(SR.GetString(SR.LockRecursionException_UpgradeAfterReadNotAllowed));
694                 }
695             }
696             else
697             {
698                 EnterMyLock();
699                 lrwc = GetThreadRWCount(false);
700
701                 if (id == upgradeLockOwnerId)
702                 {
703                     lrwc.upgradecount++;
704                     ExitMyLock();
705                     return true;
706                 }
707                 else if (id == writeLockOwnerId)
708                 {
709                     //Write lock is already held, Just update the global state 
710                     //to show presence of upgrader.
711                     Debug.Assert((owners & WRITER_HELD) > 0);
712                     owners++;
713                     upgradeLockOwnerId = id;
714                     lrwc.upgradecount++;
715                     if (lrwc.readercount > 0)
716                         fUpgradeThreadHoldingRead = true;
717                     ExitMyLock();                    
718                     return true;
719                 }
720                 else if (lrwc.readercount > 0)
721                 {
722                     //Upgrade locks may not be acquired if only read locks have been
723                     //acquired.                
724                     ExitMyLock();
725                     throw new LockRecursionException(SR.GetString(SR.LockRecursionException_UpgradeAfterReadNotAllowed));
726                 }
727             }
728
729             bool retVal = true;
730
731             int spincount = 0;
732
733             for (; ; )
734             {
735                 //Once an upgrade lock is taken, it's like having a reader lock held
736                 //until upgrade or downgrade operations are performed.              
737
738                 if ((upgradeLockOwnerId == -1) && (owners < MAX_READER))
739                 {
740                     owners++;
741                     upgradeLockOwnerId = id;
742                     break;
743                 }
744
745                 if (spincount < MaxSpinCount)
746                 {
747                     ExitMyLock();
748                     if (timeout.IsExpired)
749                         return false;
750                     spincount++;
751                     SpinWait(spincount);
752                     EnterMyLock();
753                     continue;
754                 }
755
756                 // Drat, we need to wait.  Mark that we have waiters and wait. 
757                 if (upgradeEvent == null)   // Create the needed event
758                 {
759                     LazyCreateEvent(ref upgradeEvent, true);
760                     continue;   // since we left the lock, start over. 
761                 }
762
763                 //Only one thread with the upgrade lock held can proceed.
764                 retVal = WaitOnEvent(upgradeEvent, ref numUpgradeWaiters, timeout);
765                 if (!retVal)
766                     return false;
767             }
768
769             if (fIsReentrant)
770             {
771                 //The lock may have been dropped getting here, so make a quick check to see whether some other
772                 //thread did not grab the entry.
773                 if (IsRwHashEntryChanged(lrwc))
774                     lrwc = GetThreadRWCount(false);
775                 lrwc.upgradecount++;
776             }
777
778             ExitMyLock();
779
780             return true;
781         }
782
783         public void ExitReadLock()
784         {
785             ReaderWriterCount lrwc = null;
786
787             EnterMyLock();
788
789             lrwc = GetThreadRWCount(true);
790
791             if (lrwc == null || lrwc.readercount < 1)
792             {
793                 //You have to be holding the read lock to make this call.
794                 ExitMyLock();
795                 throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedRead));
796             }
797
798             if (fIsReentrant)
799             {
800                 if (lrwc.readercount > 1)
801                 {
802                     lrwc.readercount--;
803                     ExitMyLock();
804 #if !FEATURE_NETCORE
805                     Thread.EndCriticalRegion();
806 #endif // !FEATURE_NETCORE
807                     return;
808                 }
809
810                 if (Thread.CurrentThread.ManagedThreadId == upgradeLockOwnerId)
811                 {
812                     fUpgradeThreadHoldingRead = false;
813                 }
814             }
815
816             Debug.Assert(owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
817
818             --owners;
819
820             Debug.Assert(lrwc.readercount == 1);
821             lrwc.readercount--;
822
823             ExitAndWakeUpAppropriateWaiters();
824 #if !FEATURE_NETCORE
825             Thread.EndCriticalRegion();
826 #endif // !FEATURE_NETCORE
827         }
828
829         public void ExitWriteLock()
830         {
831             ReaderWriterCount lrwc;
832             if (!fIsReentrant)
833             {
834                 if (Thread.CurrentThread.ManagedThreadId != writeLockOwnerId)
835                 {
836                     //You have to be holding the write lock to make this call.
837                     throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedWrite));
838                 }
839                 EnterMyLock();
840             }
841             else
842             {
843                 EnterMyLock();
844                 lrwc = GetThreadRWCount(false);
845
846                 if (lrwc == null)
847                 {
848                     ExitMyLock();
849                     throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedWrite));
850                 }
851
852                 if (lrwc.writercount < 1)
853                 {
854                     ExitMyLock();
855                     throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedWrite));
856                 }
857
858                 lrwc.writercount--;
859
860                 if (lrwc.writercount > 0)
861                 {
862                     ExitMyLock();
863 #if !FEATURE_NETCORE
864                     Thread.EndCriticalRegion();
865 #endif // !FEATURE_NETCORE
866                     return;
867                 }
868             }
869
870             Debug.Assert((owners & WRITER_HELD) > 0, "Calling ReleaseWriterLock when no write lock is held");
871
872             ClearWriterAcquired();
873
874             writeLockOwnerId = -1;
875
876             ExitAndWakeUpAppropriateWaiters();
877 #if !FEATURE_NETCORE
878             Thread.EndCriticalRegion();
879 #endif // !FEATURE_NETCORE
880         }
881
882         public void ExitUpgradeableReadLock()
883         {
884             ReaderWriterCount lrwc;
885             if (!fIsReentrant)
886             {
887                 if (Thread.CurrentThread.ManagedThreadId != upgradeLockOwnerId)
888                 {
889                     //You have to be holding the upgrade lock to make this call.
890                     throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedUpgrade));
891                 }
892                 EnterMyLock();
893             }
894             else
895             {
896                 EnterMyLock();
897                 lrwc = GetThreadRWCount(true);
898
899                 if (lrwc == null)
900                 {
901                     ExitMyLock();
902                     throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedUpgrade));
903                 }
904
905                 if (lrwc.upgradecount < 1)
906                 {
907                     ExitMyLock();
908                     throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedUpgrade));
909                 }
910
911                 lrwc.upgradecount--;
912
913                 if (lrwc.upgradecount > 0)
914                 {
915                     ExitMyLock();
916 #if !FEATURE_NETCORE
917                     Thread.EndCriticalRegion();
918 #endif // !FEATURE_NETCORE
919                     return;
920                 }
921
922                 fUpgradeThreadHoldingRead = false;
923             }
924
925             owners--;
926             upgradeLockOwnerId = -1;
927
928             ExitAndWakeUpAppropriateWaiters();
929 #if !FEATURE_NETCORE
930             Thread.EndCriticalRegion();
931 #endif // !FEATURE_NETCORE
932         }
933
934         /// <summary>
935         /// A routine for lazily creating a event outside the lock (so if errors
936         /// happen they are outside the lock and that we don't do much work
937         /// while holding a spin lock).  If all goes well, reenter the lock and
938         /// set 'waitEvent' 
939         /// </summary>
940         private void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
941         {
942 #if DEBUG
943             Debug.Assert(MyLockHeld);
944             Debug.Assert(waitEvent == null);
945 #endif
946             ExitMyLock();
947             EventWaitHandle newEvent;
948             if (makeAutoResetEvent)
949                 newEvent = new AutoResetEvent(false);
950             else
951                 newEvent = new ManualResetEvent(false);
952             EnterMyLock();
953             if (waitEvent == null)          // maybe someone snuck in. 
954                 waitEvent = newEvent;
955             else
956                 newEvent.Close();
957         }
958
959         /// <summary>
960         /// Waits on 'waitEvent' with a timeout  
961         /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
962         /// </summary>
963         private bool WaitOnEvent(EventWaitHandle waitEvent, ref uint numWaiters, TimeoutTracker timeout)
964         {
965 #if DEBUG
966             Debug.Assert(MyLockHeld);
967 #endif
968             waitEvent.Reset();
969             numWaiters++;
970             fNoWaiters = false;
971
972             //Setting these bits will prevent new readers from getting in.
973             if (numWriteWaiters == 1)
974                 SetWritersWaiting();
975             if (numWriteUpgradeWaiters == 1)
976                 SetUpgraderWaiting();            
977
978             bool waitSuccessful = false;
979             ExitMyLock();      // Do the wait outside of any lock
980
981             try
982             {
983                 waitSuccessful = waitEvent.WaitOne(timeout.RemainingMilliseconds);
984             }
985             finally
986             {
987                 EnterMyLock();
988                 --numWaiters;
989
990                 if (numWriteWaiters == 0 && numWriteUpgradeWaiters == 0 && numUpgradeWaiters == 0 && numReadWaiters == 0)
991                     fNoWaiters = true;
992
993                 if (numWriteWaiters == 0)
994                     ClearWritersWaiting();
995                 if (numWriteUpgradeWaiters == 0)
996                     ClearUpgraderWaiting();
997                 
998                 if (!waitSuccessful)        // We may also be aboutto throw for some reason.  Exit myLock. 
999                     ExitMyLock();
1000             }
1001             return waitSuccessful;
1002         }
1003
1004         /// <summary>
1005         /// Determines the appropriate events to set, leaves the locks, and sets the events. 
1006         /// </summary>
1007         private void ExitAndWakeUpAppropriateWaiters()
1008         {
1009 #if DEBUG
1010             Debug.Assert(MyLockHeld);
1011 #endif
1012             if (fNoWaiters)
1013             {
1014                 ExitMyLock();
1015                 return;
1016             }
1017
1018             ExitAndWakeUpAppropriateWaitersPreferringWriters();
1019         }
1020
1021         private void ExitAndWakeUpAppropriateWaitersPreferringWriters()
1022         {
1023             bool setUpgradeEvent = false;
1024             bool setReadEvent = false;
1025             uint readercount = GetNumReaders();
1026
1027             //We need this case for EU->ER->EW case, as the read count will be 2 in
1028             //that scenario.
1029             if (fIsReentrant)
1030             {
1031                 if (numWriteUpgradeWaiters > 0 && fUpgradeThreadHoldingRead && readercount == 2)
1032                 {
1033                     ExitMyLock();          // Exit before signaling to improve efficiency (wakee will need the lock)
1034                     waitUpgradeEvent.Set();     // release all upgraders (however there can be at most one). 
1035                     return;
1036                 }
1037             }
1038
1039             if (readercount == 1 && numWriteUpgradeWaiters > 0)
1040             {
1041                 //We have to be careful now, as we are droppping the lock. 
1042                 //No new writes should be allowed to sneak in if an upgrade
1043                 //was pending. 
1044
1045                 ExitMyLock();          // Exit before signaling to improve efficiency (wakee will need the lock)
1046                 waitUpgradeEvent.Set();     // release all upgraders (however there can be at most one).            
1047             }
1048             else if (readercount == 0 && numWriteWaiters > 0)
1049             {
1050                 ExitMyLock();      // Exit before signaling to improve efficiency (wakee will need the lock)
1051                 writeEvent.Set();   // release one writer. 
1052             }
1053             else if (readercount >= 0)
1054             {
1055                 if (numReadWaiters != 0 || numUpgradeWaiters != 0)
1056                 {
1057                     if (numReadWaiters != 0)
1058                         setReadEvent = true;
1059
1060                     if (numUpgradeWaiters != 0 && upgradeLockOwnerId == -1)
1061                     {
1062                         setUpgradeEvent = true;
1063                     }
1064
1065                     ExitMyLock();    // Exit before signaling to improve efficiency (wakee will need the lock)
1066
1067                     if (setReadEvent)
1068                         readEvent.Set();  // release all readers. 
1069
1070                     if (setUpgradeEvent)
1071                         upgradeEvent.Set(); //release one upgrader.
1072                 }
1073                 else
1074                     ExitMyLock();
1075             }
1076             else
1077                 ExitMyLock();
1078         }
1079
1080         private bool IsWriterAcquired()
1081         {
1082             return (owners & ~WAITING_WRITERS) == 0;
1083         }
1084
1085         private void SetWriterAcquired()
1086         {
1087             owners |= WRITER_HELD;    // indicate we have a writer.
1088         }
1089
1090         private void ClearWriterAcquired()
1091         {
1092             owners &= ~WRITER_HELD;
1093         }
1094
1095         private void SetWritersWaiting()
1096         {
1097             owners |= WAITING_WRITERS;
1098         }
1099
1100         private void ClearWritersWaiting()
1101         {
1102             owners &= ~WAITING_WRITERS;
1103         }
1104
1105         private void SetUpgraderWaiting()
1106         {
1107             owners |= WAITING_UPGRADER;
1108         }
1109
1110         private void ClearUpgraderWaiting()
1111         {
1112             owners &= ~WAITING_UPGRADER;
1113         }
1114
1115         private uint GetNumReaders()
1116         {
1117             return owners & READER_MASK;
1118         }
1119
1120 #if NET_4_5
1121                 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1122 #endif
1123         private void EnterMyLock()
1124         {
1125             if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
1126                 EnterMyLockSpin();
1127         }
1128
1129         private void EnterMyLockSpin()
1130         {
1131             int pc = Environment.ProcessorCount;
1132             for (int i = 0; ; i++)
1133             {
1134                 if (i < LockSpinCount && pc > 1)
1135                 {
1136                     Thread.SpinWait(LockSpinCycles * (i + 1));    // Wait a few dozen instructions to let another processor release lock. 
1137                 }
1138                 else if (i < (LockSpinCount + LockSleep0Count))
1139                 {
1140                     Thread.Sleep(0);        // Give up my quantum.  
1141                 }
1142                 else
1143                 {
1144                     Thread.Sleep(1);        // Give up my quantum.  
1145                 }
1146
1147                 if (myLock == 0 && Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
1148                     return;
1149             }
1150         }
1151
1152         private void ExitMyLock()
1153         {
1154             Debug.Assert(myLock != 0, "Exiting spin lock that is not held");
1155 #if NET_4_5
1156             Volatile.Write(ref myLock, 0);
1157 #else
1158             Thread.VolatileWrite(ref myLock, 0);
1159 #endif
1160         }
1161
1162 #if DEBUG
1163         private bool MyLockHeld { get { return myLock != 0; } }
1164 #endif
1165
1166         private static void SpinWait(int SpinCount)
1167         {
1168             //Exponential backoff
1169             if ((SpinCount < 5) && (Environment.ProcessorCount > 1))
1170             {
1171                 Thread.SpinWait(LockSpinCycles * SpinCount);
1172             }
1173             else if (SpinCount < MaxSpinCount - 3)
1174             {
1175                 Thread.Sleep(0);
1176             }
1177             else
1178             {
1179                 Thread.Sleep(1);
1180             }
1181         }
1182
1183         public void Dispose()
1184         {
1185             Dispose(true);
1186                
1187         }
1188
1189         private void Dispose(bool disposing)
1190         {
1191             if(disposing && !fDisposed)
1192             {  
1193                 if(WaitingReadCount>0 || WaitingUpgradeCount > 0 || WaitingWriteCount > 0) 
1194                     throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_IncorrectDispose));
1195
1196                 if(IsReadLockHeld || IsUpgradeableReadLockHeld || IsWriteLockHeld)
1197                     throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_IncorrectDispose));
1198
1199                 if (writeEvent != null)
1200                 {
1201                     writeEvent.Close();
1202                     writeEvent = null;
1203                 }
1204
1205                 if (readEvent != null)
1206                 {
1207                     readEvent.Close();
1208                     readEvent = null;
1209                 }
1210
1211                 if (upgradeEvent != null)
1212                 {
1213                     upgradeEvent.Close();
1214                     upgradeEvent = null;
1215                 }
1216
1217                 if (waitUpgradeEvent != null)
1218                 {
1219                     waitUpgradeEvent.Close();
1220                     waitUpgradeEvent = null;
1221                 }
1222
1223                 fDisposed = true;
1224             }
1225         }
1226
1227         public bool IsReadLockHeld
1228         {
1229             get
1230             {
1231                 if (RecursiveReadCount > 0)
1232                     return true;
1233                 else
1234                     return false;
1235             }
1236         }
1237
1238         public bool IsUpgradeableReadLockHeld
1239         {
1240             get
1241             {
1242                 if (RecursiveUpgradeCount > 0)
1243                     return true;
1244                 else
1245                     return false;
1246             }
1247         }
1248
1249         public bool IsWriteLockHeld
1250         {
1251             get
1252             {
1253                 if (RecursiveWriteCount > 0)
1254                     return true;
1255                 else
1256                     return false;
1257             }
1258         }
1259
1260         public LockRecursionPolicy RecursionPolicy
1261         {
1262             get
1263             {
1264                 if (fIsReentrant)
1265                 {
1266                     return LockRecursionPolicy.SupportsRecursion;
1267                 }
1268                 else
1269                 {
1270                     return LockRecursionPolicy.NoRecursion;
1271                 }
1272             }
1273
1274         }
1275
1276         public int CurrentReadCount
1277         {
1278             get
1279             {
1280                 int numreaders = (int)GetNumReaders();
1281
1282                 if (upgradeLockOwnerId != -1)
1283                     return numreaders - 1;
1284                 else
1285                     return numreaders;
1286             }
1287         }
1288
1289      
1290         public int RecursiveReadCount
1291         {
1292             get
1293             {
1294                 int count = 0;
1295                 ReaderWriterCount lrwc = GetThreadRWCount(true);
1296                 if(lrwc != null)
1297                     count = lrwc.readercount;
1298
1299                 return count;                
1300             }
1301         }
1302
1303         public int RecursiveUpgradeCount
1304         {
1305             get
1306             {
1307                 if (fIsReentrant)
1308                 {
1309                     int count = 0;
1310
1311                     ReaderWriterCount lrwc = GetThreadRWCount(true);
1312                     if(lrwc != null)
1313                         count = lrwc.upgradecount;
1314
1315                     return count;
1316                 }
1317                 else
1318                 {
1319                     if (Thread.CurrentThread.ManagedThreadId == upgradeLockOwnerId)
1320                         return 1;
1321                     else
1322                         return 0;
1323                 }
1324             }
1325         }
1326
1327         public int RecursiveWriteCount
1328         {
1329             get
1330             {
1331
1332                 if (fIsReentrant)
1333                 {
1334                     int count = 0;
1335
1336                     ReaderWriterCount lrwc = GetThreadRWCount(true);
1337                     if(lrwc != null)
1338                         count = lrwc.writercount; 
1339
1340                     return count;
1341                 }
1342                 else
1343                 {
1344                     if (Thread.CurrentThread.ManagedThreadId == writeLockOwnerId)
1345                         return 1;
1346                     else
1347                         return 0;
1348                 }
1349             }
1350         }
1351
1352         public int WaitingReadCount
1353         {
1354             get
1355             {
1356                 return (int)numReadWaiters;
1357             }
1358         }
1359
1360         public int WaitingUpgradeCount
1361         {
1362             get
1363             {
1364                 return (int)numUpgradeWaiters;
1365             }
1366         }
1367
1368         public int WaitingWriteCount
1369         {
1370             get
1371             {
1372                 return (int)numWriteWaiters;
1373             }
1374         }
1375     }
1376 }
1377
1378
1379
1380
1381
1382
1383
1384
1385