importing messaging-2008 branch to trunk, going on.
[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                 // Are we on a multiprocessor?
60                 static readonly bool smp;
61                 
62                 // Lock specifiation for myLock:  This lock protects exactly the local fields associted
63                 // instance of MyReaderWriterLock.  It does NOT protect the memory associted with the
64                 // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
65                 int myLock;
66
67                 // Who owns the lock owners > 0 => readers
68                 // owners = -1 means there is one writer, Owners must be >= -1.  
69                 int owners;
70                 Thread upgradable_thread;
71                 
72                 // These variables allow use to avoid Setting events (which is expensive) if we don't have to. 
73                 uint numWriteWaiters;        // maximum number of threads that can be doing a WaitOne on the writeEvent 
74                 uint numReadWaiters;         // maximum number of threads that can be doing a WaitOne on the readEvent
75                 uint numUpgradeWaiters;      // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1). 
76                 
77                 // conditions we wait on. 
78                 EventWaitHandle writeEvent;    // threads waiting to aquire a write lock go here.
79                 EventWaitHandle readEvent;     // threads waiting to aquire a read lock go here (will be released in bulk)
80                 EventWaitHandle upgradeEvent;  // thread waiting to upgrade a read lock to a write lock go here (at most one)
81
82                 //int lock_owner;
83
84                 // Only set if we are a recursive lock
85                 //Dictionary<int,int> reader_locks;
86
87                 readonly LockRecursionPolicy recursionPolicy;
88                 bool is_disposed;
89                 
90                 static ReaderWriterLockSlim ()
91                 {
92                         smp = Environment.ProcessorCount > 1;
93                 }
94                 
95                 public ReaderWriterLockSlim ()
96                 {
97                         // NoRecursion (0) is the default value
98                 }
99
100                 public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
101                 {
102                         this.recursionPolicy = recursionPolicy;
103                         
104                         if (recursionPolicy != LockRecursionPolicy.NoRecursion){
105                                 //reader_locks = new Dictionary<int,int> ();
106                                 throw new NotImplementedException ("recursionPolicy != NoRecursion not currently implemented");
107                         }
108                 }
109
110                 public void EnterReadLock ()
111                 {
112                         TryEnterReadLock (-1);
113                 }
114
115                 public bool TryEnterReadLock (int millisecondsTimeout)
116                 {
117                         if (millisecondsTimeout < Timeout.Infinite)
118                                 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
119                         
120                         if (is_disposed)
121                                 throw new ObjectDisposedException (null);
122                         
123                         EnterMyLock ();
124                         
125                         while (true){
126                                 // Easy case, no contention
127                                 // owners >= 0 means there might be readers (but no writer)
128                                 if (owners >= 0 && numWriteWaiters == 0){
129                                         owners++;
130                                         break;
131                                 }
132                                 
133                                 // If the request is to probe.
134                                 if (millisecondsTimeout == 0){
135                                         ExitMyLock ();
136                                         return false;
137                                 }
138
139                                 // We need to wait.  Mark that we have waiters and wait.  
140                                 if (readEvent == null) {
141                                         LazyCreateEvent (ref readEvent, false);
142                                         // since we left the lock, start over. 
143                                         continue;
144                                 }
145
146                                 if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
147                                         return false;
148                         }
149                         ExitMyLock ();
150                         
151                         return true;
152                 }
153
154                 public bool TryEnterReadLock (TimeSpan timeout)
155                 {
156                         int ms = CheckTimeout (timeout);
157                         return TryEnterReadLock (ms);
158                 }
159
160                 //
161                 // TODO: What to do if we are releasing a ReadLock and we do not own it?
162                 //
163                 public void ExitReadLock ()
164                 {
165                         EnterMyLock ();
166                         Debug.Assert(owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
167                         --owners;
168                         ExitAndWakeUpAppropriateWaiters ();
169                 }
170
171                 public void EnterWriteLock ()
172                 {
173                         TryEnterWriteLock (-1);
174                 }
175                 
176                 public bool TryEnterWriteLock (int millisecondsTimeout)
177                 {
178                         EnterMyLock ();
179
180                         while (true){
181                                 // There is no contention, we are done
182                                 if (owners == 0){
183                                         // Indicate that we have a writer
184                                         owners = -1;
185                                         break;
186                                 }
187
188                                 // If we are the thread that took the Upgradable read lock
189                                 if (owners == 1 && upgradable_thread == Thread.CurrentThread){
190                                         owners = -1;
191                                         break;
192                                 }
193
194                                 // If the request is to probe.
195                                 if (millisecondsTimeout == 0){
196                                         ExitMyLock ();
197                                         return false;
198                                 }
199
200                                 // We need to wait, figure out what kind of waiting.
201                                 
202                                 if (upgradable_thread == Thread.CurrentThread){
203                                         // We are the upgradable thread, register our interest.
204                                         
205                                         if (upgradeEvent == null){
206                                                 LazyCreateEvent (ref upgradeEvent, false);
207
208                                                 // since we left the lock, start over.
209                                                 continue;
210                                         }
211
212                                         if (numUpgradeWaiters > 0){
213                                                 ExitMyLock ();
214                                                 throw new ApplicationException ("Upgrading lock to writer lock already in process, deadlock");
215                                         }
216                                         
217                                         if (!WaitOnEvent (upgradeEvent, ref numUpgradeWaiters, millisecondsTimeout))
218                                                 return false;
219                                 } else {
220                                         if (writeEvent == null){
221                                                 LazyCreateEvent (ref writeEvent, true);
222
223                                                 // since we left the lock, retry
224                                                 continue;
225                                         }
226                                         if (!WaitOnEvent (writeEvent, ref numWriteWaiters, millisecondsTimeout))
227                                                 return false;
228                                 }
229                         }
230
231                         Debug.Assert (owners == -1, "Owners is not -1");
232                         ExitMyLock ();
233                         return true;
234                 }
235
236                 public bool TryEnterWriteLock (TimeSpan timeout)
237                 {
238                         return TryEnterWriteLock (CheckTimeout (timeout));
239                 }
240
241                 public void ExitWriteLock ()
242                 {
243                         EnterMyLock ();
244                         Debug.Assert (owners == -1, "Calling ReleaseWriterLock when no write lock is held");
245                         Debug.Assert (numUpgradeWaiters > 0);
246                         upgradable_thread = null;
247                         owners = 0;
248                         ExitAndWakeUpAppropriateWaiters ();
249                 }
250
251                 public void EnterUpgradeableReadLock ()
252                 {
253                         TryEnterUpgradeableReadLock (-1);
254                 }
255
256                 //
257                 // Taking the Upgradable read lock is like taking a read lock
258                 // but we limit it to a single upgradable at a time.
259                 //
260                 public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
261                 {
262                         EnterMyLock ();
263                         while (true){
264                                 if (owners == 0 && numWriteWaiters == 0 && upgradable_thread == null){
265                                         owners++;
266                                         upgradable_thread = Thread.CurrentThread;
267                                         break;
268                                 }
269
270                                 // If the request is to probe
271                                 if (millisecondsTimeout == 0){
272                                         ExitMyLock ();
273                                         return false;
274                                 }
275
276                                 if (readEvent == null){
277                                         LazyCreateEvent (ref readEvent, false);
278                                         // since we left the lock, start over.
279                                         continue;
280                                 }
281
282                                 if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
283                                         return false;
284                         }
285
286                         ExitMyLock ();
287                         return true;
288                 }
289
290                 public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
291                 {
292                         return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
293                 }
294                
295                 public void ExitUpgradeableReadLock ()
296                 {
297                         EnterMyLock ();
298                         Debug.Assert (owners > 0, "Releasing an upgradable lock, but there was no reader!");
299                         --owners;
300                         upgradable_thread = null;
301                         ExitAndWakeUpAppropriateWaiters ();
302                 }
303
304                 public void Dispose ()
305                 {
306                         is_disposed = true;
307                 }
308
309                 public bool IsReadLockHeld {
310                         get { throw new NotImplementedException (); }
311                 }
312                 
313                 public bool IsWriteLockHeld {
314                         get { throw new NotImplementedException (); }
315                 }
316                 
317                 public bool IsUpgradeableReadLockHeld {
318                         get { throw new NotImplementedException (); }
319                 }
320
321                 public int CurrentReadCount {
322                         get { throw new NotImplementedException (); }
323                 }
324                 
325                 public int RecursiveReadCount {
326                         get { throw new NotImplementedException (); }
327                 }
328
329                 public int RecursiveUpgradeCount {
330                         get { throw new NotImplementedException (); }
331                 }
332
333                 public int RecursiveWriteCount {
334                         get { throw new NotImplementedException (); }
335                 }
336
337                 public int WaitingReadCount {
338                         get { throw new NotImplementedException (); }
339                 }
340
341                 public int WaitingUpgradeCount {
342                         get { throw new NotImplementedException (); }
343                 }
344
345                 public int WaitingWriteCount {
346                         get { throw new NotImplementedException (); }
347                 }
348
349                 public LockRecursionPolicy RecursionPolicy {
350                         get { return recursionPolicy; }
351                 }
352                 
353 #region Private methods
354                 void EnterMyLock ()
355                 {
356                         if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
357                                 EnterMyLockSpin ();
358                 }
359
360                 void EnterMyLockSpin ()
361                 {
362
363                         for (int i = 0; ;i++) {
364                                 if (i < 3 && smp)
365                                         Thread.SpinWait (20);    // Wait a few dozen instructions to let another processor release lock. 
366                                 else 
367                                         Thread.Sleep (0);        // Give up my quantum.  
368
369                                 if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
370                                         return;
371                         }
372                 }
373
374                 void ExitMyLock()
375                 {
376                         Debug.Assert (myLock != 0, "Exiting spin lock that is not held");
377                         myLock = 0;
378                 }
379
380                 bool MyLockHeld { get { return myLock != 0; } }
381
382                 /// <summary>
383                 /// Determines the appropriate events to set, leaves the locks, and sets the events. 
384                 /// </summary>
385                 private void ExitAndWakeUpAppropriateWaiters()
386                 {
387                         Debug.Assert (MyLockHeld);
388
389                         // First a writing thread waiting on being upgraded
390                         if (owners == 1 && numUpgradeWaiters != 0){
391                                 // Exit before signaling to improve efficiency (wakee will need the lock)
392                                 ExitMyLock ();
393                                 // release all upgraders (however there can be at most one). 
394                                 upgradeEvent.Set ();
395                                 //
396                                 // TODO: What does the following comment mean?
397                                 // two threads upgrading is a guarenteed deadlock, so we throw in that case. 
398                         } else if (owners == 0 && numWriteWaiters > 0) {
399                                 // Exit before signaling to improve efficiency (wakee will need the lock)
400                                 ExitMyLock ();
401                                 // release one writer. 
402                                 writeEvent.Set ();
403                         }
404                         else if (owners >= 0 && numReadWaiters != 0) {
405                                 // Exit before signaling to improve efficiency (wakee will need the lock)
406                                 ExitMyLock ();
407                                 // release all readers.
408                                 readEvent.Set();
409                         } else
410                                 ExitMyLock();
411                 }
412
413                 /// <summary>
414                 /// A routine for lazily creating a event outside the lock (so if errors
415                 /// happen they are outside the lock and that we don't do much work
416                 /// while holding a spin lock).  If all goes well, reenter the lock and
417                 /// set 'waitEvent' 
418                 /// </summary>
419                 void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
420                 {
421                         Debug.Assert (MyLockHeld);
422                         Debug.Assert (waitEvent == null);
423                         
424                         ExitMyLock ();
425                         EventWaitHandle newEvent;
426                         if (makeAutoResetEvent) 
427                                 newEvent = new AutoResetEvent (false);
428                         else 
429                                 newEvent = new ManualResetEvent (false);
430
431                         EnterMyLock ();
432
433                         // maybe someone snuck in. 
434                         if (waitEvent == null)
435                                 waitEvent = newEvent;
436                 }
437
438                 /// <summary>
439                 /// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.  
440                 /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
441                 /// </summary>
442                 bool WaitOnEvent (EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
443                 {
444                         Debug.Assert (MyLockHeld);
445
446                         waitEvent.Reset ();
447                         numWaiters++;
448
449                         bool waitSuccessful = false;
450
451                         // Do the wait outside of any lock 
452                         ExitMyLock();      
453                         try {
454                                 waitSuccessful = waitEvent.WaitOne (millisecondsTimeout, false);
455                         } finally {
456                                 EnterMyLock ();
457                                 --numWaiters;
458                                 if (!waitSuccessful)
459                                         ExitMyLock ();
460                         }
461                         return waitSuccessful;
462                 }
463                 
464                 static int CheckTimeout (TimeSpan timeout)
465                 {
466                         try {
467                                 return checked((int) timeout.TotalMilliseconds);
468                         } catch (System.OverflowException) {
469                                 throw new ArgumentOutOfRangeException ("timeout");                              
470                         }
471                 }
472 #endregion
473         }
474 }