2008-12-02 Marek Habersack <mhabersack@novell.com>
[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 //
10 // Copyright 2004-2008 Novell, Inc (http://www.novell.com)
11 // Copyright 2003, Ximian, Inc.
12 //
13 // NoRecursion code based on the blog post from Vance Morrison:
14 //   http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
15 //
16 // Recursion code based on Mono's implementation of ReaderWriterLock.
17 // 
18 // Permission is hereby granted, free of charge, to any person obtaining
19 // a copy of this software and associated documentation files (the
20 // "Software"), to deal in the Software without restriction, including
21 // without limitation the rights to use, copy, modify, merge, publish,
22 // distribute, sublicense, and/or sell copies of the Software, and to
23 // permit persons to whom the Software is furnished to do so, subject to
24 // the following conditions:
25 // 
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
28 // 
29 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 //
37
38 using System;
39 using System.Collections;
40 using System.Collections.Generic;
41 using System.Security.Permissions;
42 using System.Diagnostics;
43 using System.Threading;
44
45 namespace System.Threading {
46
47         //
48         // This implementation is based on the light-weight
49         // Reader/Writer lock sample from Vance Morrison's blog:
50         //
51         // http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
52         //
53         // And in Mono's ReaderWriterLock
54         //
55         [HostProtectionAttribute(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
56         [HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
57         public class ReaderWriterLockSlim : IDisposable {
58                 // Are we on a multiprocessor?
59                 static bool smp;
60                 
61                 // Lock specifiation for myLock:  This lock protects exactly the local fields associted
62                 // instance of MyReaderWriterLock.  It does NOT protect the memory associted with the
63                 // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
64                 int myLock;
65
66                 // Who owns the lock owners > 0 => readers
67                 // owners = -1 means there is one writer, Owners must be >= -1.  
68                 internal int owners;
69                 Thread upgradable_thread;
70                 
71                 // These variables allow use to avoid Setting events (which is expensive) if we don't have to. 
72                 uint numWriteWaiters;        // maximum number of threads that can be doing a WaitOne on the writeEvent 
73                 uint numReadWaiters;         // maximum number of threads that can be doing a WaitOne on the readEvent
74                 uint numUpgradeWaiters;      // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1). 
75                 
76                 // conditions we wait on. 
77                 EventWaitHandle writeEvent;    // threads waiting to aquire a write lock go here.
78                 EventWaitHandle readEvent;     // threads waiting to aquire a read lock go here (will be released in bulk)
79                 EventWaitHandle upgradeEvent;  // thread waiting to upgrade a read lock to a write lock go here (at most one)
80
81                 int lock_owner;
82
83                 // Only set if we are a recursive lock
84                 Dictionary<int,int> reader_locks;
85                 
86                 static ReaderWriterLockSlim ()
87                 {
88                         smp = Environment.ProcessorCount > 1;
89                 }
90                 
91                 public ReaderWriterLockSlim () : this (LockRecursionPolicy.NoRecursion)
92                 {
93                 }
94
95                 public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
96                 {
97                         if (recursionPolicy != LockRecursionPolicy.NoRecursion){
98                                 reader_locks = new Dictionary<int,int> ();
99                                 throw new NotImplementedException ("recursionPolicy != NoRecursion not currently implemented");
100                         }
101                 }
102
103                 public void EnterReadLock ()
104                 {
105                         TryEnterReadLock (-1);
106                 }
107
108                 public bool TryEnterReadLock (int millisecondsTimeout)
109                 {
110                         EnterMyLock ();
111                         
112                         while (true){
113                                 // Easy case, no contention
114                                 // owners >= 0 means there might be readers (but no writer)
115                                 if (owners >= 0 && numWriteWaiters == 0){
116                                         owners++;
117                                         break;
118                                 }
119                                 
120                                 // If the request is to probe.
121                                 if (millisecondsTimeout == 0){
122                                         ExitMyLock ();
123                                         return false;
124                                 }
125
126                                 // We need to wait.  Mark that we have waiters and wait.  
127                                 if (readEvent == null) {
128                                         LazyCreateEvent (ref readEvent, false);
129                                         // since we left the lock, start over. 
130                                         continue;
131                                 }
132
133                                 if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
134                                         return false;
135                         }
136                         ExitMyLock ();
137                         
138                         return true;
139                 }
140
141                 public bool TryEnterReadLock (TimeSpan timeout)
142                 {
143                         int ms = CheckTimeout (timeout);
144                         return TryEnterReadLock (ms);
145                 }
146
147                 //
148                 // TODO: What to do if we are releasing a ReadLock and we do not own it?
149                 //
150                 public void ExitReadLock ()
151                 {
152                         EnterMyLock ();
153                         Debug.Assert(owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
154                         --owners;
155                         ExitAndWakeUpAppropriateWaiters ();
156                 }
157
158                 public void EnterWriteLock ()
159                 {
160                         TryEnterWriteLock (-1);
161                 }
162                 
163                 public bool TryEnterWriteLock (int millisecondsTimeout)
164                 {
165                         EnterMyLock ();
166
167                         while (true){
168                                 // There is no contention, we are done
169                                 if (owners == 0){
170                                         // Indicate that we have a writer
171                                         owners = -1;
172                                         break;
173                                 }
174
175                                 // If we are the thread that took the Upgradable read lock
176                                 if (owners == 1 && upgradable_thread == Thread.CurrentThread){
177                                         owners = -1;
178                                         break;
179                                 }
180
181                                 // If the request is to probe.
182                                 if (millisecondsTimeout == 0){
183                                         ExitMyLock ();
184                                         return false;
185                                 }
186
187                                 // We need to wait, figure out what kind of waiting.
188                                 
189                                 if (upgradable_thread == Thread.CurrentThread){
190                                         // We are the upgradable thread, register our interest.
191                                         
192                                         if (upgradeEvent == null){
193                                                 LazyCreateEvent (ref upgradeEvent, false);
194
195                                                 // since we left the lock, start over.
196                                                 continue;
197                                         }
198
199                                         if (numUpgradeWaiters > 0){
200                                                 ExitMyLock ();
201                                                 throw new ApplicationException ("Upgrading lock to writer lock already in process, deadlock");
202                                         }
203                                         
204                                         if (!WaitOnEvent (upgradeEvent, ref numUpgradeWaiters, millisecondsTimeout))
205                                                 return false;
206                                 } else {
207                                         if (writeEvent == null){
208                                                 LazyCreateEvent (ref writeEvent, true);
209
210                                                 // since we left the lock, retry
211                                                 continue;
212                                         }
213                                         if (!WaitOnEvent (writeEvent, ref numWriteWaiters, millisecondsTimeout))
214                                                 return false;
215                                 }
216                         }
217
218                         Debug.Assert (owners == -1, "Owners is not -1");
219                         ExitMyLock ();
220                         return true;
221                 }
222
223                 public bool TryEnterWriteLock (TimeSpan timeout)
224                 {
225                         int ms = CheckTimeout (timeout);
226                         return TryEnterWriteLock (ms);
227                 }
228
229                 public void ExitWriteLock ()
230                 {
231                         EnterMyLock ();
232                         Debug.Assert (owners == -1, "Calling ReleaseWriterLock when no write lock is held");
233                         Debug.Assert (numUpgradeWaiters > 0);
234                         upgradable_thread = null;
235                         owners = 0;
236                         ExitAndWakeUpAppropriateWaiters ();
237                 }
238
239                 public void EnterUpgradableReadLock ()
240                 {
241                         TryEnterUpgradableReadLock (-1);
242                 }
243
244                 //
245                 // Taking the Upgradable read lock is like taking a read lock
246                 // but we limit it to a single upgradable at a time.
247                 //
248                 public bool TryEnterUpgradableReadLock (int millisecondsTimeout)
249                 {
250                         EnterMyLock ();
251                         while (true){
252                                 if (owners == 0 && numWriteWaiters == 0 && upgradable_thread == null){
253                                         owners++;
254                                         upgradable_thread = Thread.CurrentThread;
255                                         break;
256                                 }
257
258                                 // If the request is to probe
259                                 if (millisecondsTimeout == 0){
260                                         ExitMyLock ();
261                                         return false;
262                                 }
263
264                                 if (readEvent == null){
265                                         LazyCreateEvent (ref readEvent, false);
266                                         // since we left the lock, start over.
267                                         continue;
268                                 }
269
270                                 if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
271                                         return false;
272                         }
273
274                         ExitMyLock ();
275                         return true;
276                 }
277
278                 public bool TryEnterUpgradableReadLock (TimeSpan timeout)
279                 {
280                         int ms = CheckTimeout (timeout);
281                         return TryEnterUpgradableReadLock (ms);
282                 }
283                
284                 public void ExitUpgradeableReadLock ()
285                 {
286                         EnterMyLock ();
287                         Debug.Assert (owners > 0, "Releasing an upgradable lock, but there was no reader!");
288                         --owners;
289                         upgradable_thread = null;
290                         ExitAndWakeUpAppropriateWaiters ();
291                 }
292
293                 public void Dispose ()
294                 {
295                         throw new NotImplementedException ();
296                 }
297                 
298 #region Private methods
299                 void EnterMyLock ()
300                 {
301                         if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
302                                 EnterMyLockSpin ();
303                 }
304
305                 void EnterMyLockSpin ()
306                 {
307
308                         for (int i = 0; ;i++) {
309                                 if (i < 3 && smp)
310                                         Thread.SpinWait (20);    // Wait a few dozen instructions to let another processor release lock. 
311                                 else 
312                                         Thread.Sleep (0);        // Give up my quantum.  
313
314                                 if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
315                                         return;
316                         }
317                 }
318
319                 void ExitMyLock()
320                 {
321                         Debug.Assert (myLock != 0, "Exiting spin lock that is not held");
322                         myLock = 0;
323                 }
324
325                 bool MyLockHeld { get { return myLock != 0; } }
326
327                 /// <summary>
328                 /// Determines the appropriate events to set, leaves the locks, and sets the events. 
329                 /// </summary>
330                 private void ExitAndWakeUpAppropriateWaiters()
331                 {
332                         Debug.Assert (MyLockHeld);
333
334                         // First a writing thread waiting on being upgraded
335                         if (owners == 1 && numUpgradeWaiters != 0){
336                                 // Exit before signaling to improve efficiency (wakee will need the lock)
337                                 ExitMyLock ();
338                                 // release all upgraders (however there can be at most one). 
339                                 upgradeEvent.Set ();
340                                 //
341                                 // TODO: What does the following comment mean?
342                                 // two threads upgrading is a guarenteed deadlock, so we throw in that case. 
343                         } else if (owners == 0 && numWriteWaiters > 0) {
344                                 // Exit before signaling to improve efficiency (wakee will need the lock)
345                                 ExitMyLock ();
346                                 // release one writer. 
347                                 writeEvent.Set ();
348                         }
349                         else if (owners >= 0 && numReadWaiters != 0) {
350                                 // Exit before signaling to improve efficiency (wakee will need the lock)
351                                 ExitMyLock ();
352                                 // release all readers.
353                                 readEvent.Set();
354                         } else
355                                 ExitMyLock();
356                 }
357
358                 /// <summary>
359                 /// A routine for lazily creating a event outside the lock (so if errors
360                 /// happen they are outside the lock and that we don't do much work
361                 /// while holding a spin lock).  If all goes well, reenter the lock and
362                 /// set 'waitEvent' 
363                 /// </summary>
364                 void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
365                 {
366                         Debug.Assert (MyLockHeld);
367                         Debug.Assert (waitEvent == null);
368                         
369                         ExitMyLock ();
370                         EventWaitHandle newEvent;
371                         if (makeAutoResetEvent) 
372                                 newEvent = new AutoResetEvent (false);
373                         else 
374                                 newEvent = new ManualResetEvent (false);
375
376                         EnterMyLock ();
377
378                         // maybe someone snuck in. 
379                         if (waitEvent == null)
380                                 waitEvent = newEvent;
381                 }
382
383                 /// <summary>
384                 /// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.  
385                 /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
386                 /// </summary>
387                 bool WaitOnEvent (EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
388                 {
389                         Debug.Assert (MyLockHeld);
390
391                         waitEvent.Reset ();
392                         numWaiters++;
393
394                         bool waitSuccessful = false;
395
396                         // Do the wait outside of any lock 
397                         ExitMyLock();      
398                         try {
399                                 waitSuccessful = waitEvent.WaitOne (millisecondsTimeout, false);
400                         } finally {
401                                 EnterMyLock ();
402                                 --numWaiters;
403                                 if (!waitSuccessful)
404                                         ExitMyLock ();
405                         }
406                         return waitSuccessful;
407                 }
408                 
409                 int CheckTimeout (TimeSpan timeout)
410                 {
411                         int ms = (int) timeout.TotalMilliseconds;
412
413                         if (ms < -1)
414                                 throw new ArgumentOutOfRangeException ("timeout",
415                                                 "Number must be either non-negative or -1");
416                         return ms;
417                 }
418 #endregion
419         }
420 }