Allow recursive Write + n * Read lock pattern for ReaderWriterLockSlim and add corres...
[mono.git] / mcs / class / System.Core / System.Threading / ReaderWriterLockSlim.cs
1 //
2 // System.Threading.ReaderWriterLockSlim.cs
3 //
4 // Author:
5 //       Jérémie "Garuma" Laval <jeremie.laval@gmail.com>
6 //
7 // Copyright (c) 2010 Jérémie "Garuma" Laval
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Security.Permissions;
33 using System.Diagnostics;
34 using System.Threading;
35
36 namespace System.Threading {
37
38         [HostProtectionAttribute(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
39         [HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
40         public class ReaderWriterLockSlim : IDisposable
41         {
42                 /* Position of each bit isn't really important 
43                  * but their relative order is
44                  */
45                 const int RwReadBit = 3;
46
47                 const int RwWait = 1;
48                 const int RwWaitUpgrade = 2;
49                 const int RwWrite = 4;
50                 const int RwRead = 8;
51
52                 int rwlock;
53                 
54                 readonly LockRecursionPolicy recursionPolicy;
55
56                 AtomicBoolean upgradableTaken = new AtomicBoolean ();
57 #if NET_4_0
58                 ManualResetEventSlim upgradableEvent = new ManualResetEventSlim (true);
59                 ManualResetEventSlim writerDoneEvent = new ManualResetEventSlim (true);
60                 ManualResetEventSlim readerDoneEvent = new ManualResetEventSlim (true);
61 #else
62                 ManualResetEvent upgradableEvent = new ManualResetEvent (true);
63                 ManualResetEvent writerDoneEvent = new ManualResetEvent (true);
64                 ManualResetEvent readerDoneEvent = new ManualResetEvent (true);
65 #endif
66
67                 int numReadWaiters, numUpgradeWaiters, numWriteWaiters;
68                 bool disposed;
69
70                 static int idPool = int.MinValue;
71                 readonly int id = Interlocked.Increment (ref idPool);
72
73                 [ThreadStatic]
74                 static IDictionary<int, ThreadLockState> currentThreadState;
75
76                 public ReaderWriterLockSlim () : this (LockRecursionPolicy.NoRecursion)
77                 {
78                 }
79
80                 public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
81                 {
82                         this.recursionPolicy = recursionPolicy;
83                 }
84
85                 public void EnterReadLock ()
86                 {
87                         TryEnterReadLock (-1);
88                 }
89
90                 public bool TryEnterReadLock (int millisecondsTimeout)
91                 {
92                         ThreadLockState ctstate = CurrentThreadState;
93
94                         if (CheckState (millisecondsTimeout, LockState.Read)) {
95                                 ctstate.ReaderRecursiveCount++;
96                                 return true;
97                         }
98
99                         // This is downgrading from upgradable, no need for check since
100                         // we already have a sort-of read lock that's going to disappear
101                         // after user calls ExitUpgradeableReadLock.
102                         // Same idea when recursion is allowed and a write thread wants to
103                         // go for a Read too.
104                         if (CurrentLockState.Has (LockState.Upgradable)
105                             || recursionPolicy == LockRecursionPolicy.SupportsRecursion) {
106                                 Interlocked.Add (ref rwlock, RwRead);
107                                 ctstate.LockState ^= LockState.Read;
108                                 ctstate.ReaderRecursiveCount++;
109
110                                 return true;
111                         }
112                         
113                         Stopwatch sw = Stopwatch.StartNew ();
114                         Interlocked.Increment (ref numReadWaiters);
115
116                         while (millisecondsTimeout == -1 || sw.ElapsedMilliseconds < millisecondsTimeout) {
117                                 if ((rwlock & 0x7) > 0) {
118                                         writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
119                                         continue;
120                                 }
121
122                                 if ((Interlocked.Add (ref rwlock, RwRead) & 0x7) == 0) {
123                                         ctstate.LockState ^= LockState.Read;
124                                         ctstate.ReaderRecursiveCount++;
125                                         Interlocked.Decrement (ref numReadWaiters);
126                                         if (readerDoneEvent.IsSet ())
127                                                 readerDoneEvent.Reset ();
128                                         return true;
129                                 }
130
131                                 Interlocked.Add (ref rwlock, -RwRead);
132
133                                 writerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
134                         }
135
136                         Interlocked.Decrement (ref numReadWaiters);
137                         return false;
138                 }
139
140                 public bool TryEnterReadLock (TimeSpan timeout)
141                 {
142                         return TryEnterReadLock (CheckTimeout (timeout));
143                 }
144
145                 public void ExitReadLock ()
146                 {
147                         ThreadLockState ctstate = CurrentThreadState;
148
149                         if (!ctstate.LockState.Has (LockState.Read))
150                                 throw new SynchronizationLockException ("The current thread has not entered the lock in read mode");
151
152                         ctstate.LockState ^= LockState.Read;
153                         ctstate.ReaderRecursiveCount--;
154                         if (Interlocked.Add (ref rwlock, -RwRead) >> RwReadBit == 0)
155                                 readerDoneEvent.Set ();
156                 }
157
158                 public void EnterWriteLock ()
159                 {
160                         TryEnterWriteLock (-1);
161                 }
162                 
163                 public bool TryEnterWriteLock (int millisecondsTimeout)
164                 {
165                         ThreadLockState ctstate = CurrentThreadState;
166
167                         if (CheckState (millisecondsTimeout, LockState.Write)) {
168                                 ctstate.WriterRecursiveCount++;
169                                 return true;
170                         }
171
172                         Stopwatch sw = Stopwatch.StartNew ();
173                         Interlocked.Increment (ref numWriteWaiters);
174                         bool isUpgradable = ctstate.LockState.Has (LockState.Upgradable);
175
176                         // If the code goes there that means we had a read lock beforehand
177                         if (isUpgradable && rwlock >= RwRead)
178                                 Interlocked.Add (ref rwlock, -RwRead);
179
180                         int stateCheck = isUpgradable ? RwWaitUpgrade : RwWait;
181                         int appendValue = RwWait | (isUpgradable ? RwWaitUpgrade : 0);
182
183                         while (millisecondsTimeout < 0 || sw.ElapsedMilliseconds < millisecondsTimeout) {
184                                 int state = rwlock;
185
186                                 if (state <= stateCheck) {
187                                         if (Interlocked.CompareExchange (ref rwlock, RwWrite, state) == state) {
188                                                 ctstate.LockState ^= LockState.Write;
189                                                 ctstate.WriterRecursiveCount++;
190                                                 Interlocked.Decrement (ref numWriteWaiters);
191                                                 if (writerDoneEvent.IsSet ())
192                                                         writerDoneEvent.Reset ();
193                                                 return true;
194                                         }
195                                         state = rwlock;
196                                 }
197
198                                 while ((state & RwWait) == 0 && Interlocked.CompareExchange (ref rwlock, state | appendValue, state) == state)
199                                         state = rwlock;
200
201                                 while (rwlock > stateCheck && (millisecondsTimeout < 0 || sw.ElapsedMilliseconds < millisecondsTimeout))
202                                         readerDoneEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
203                         }
204
205                         Interlocked.Decrement (ref numWriteWaiters);
206                         return false;
207                 }
208
209                 public bool TryEnterWriteLock (TimeSpan timeout)
210                 {
211                         return TryEnterWriteLock (CheckTimeout (timeout));
212                 }
213
214                 public void ExitWriteLock ()
215                 {
216                         ThreadLockState ctstate = CurrentThreadState;
217
218                         if (!ctstate.LockState.Has (LockState.Write))
219                                 throw new SynchronizationLockException ("The current thread has not entered the lock in write mode");
220                         
221                         ctstate.LockState ^= LockState.Write;
222                         ctstate.WriterRecursiveCount--;
223                         Interlocked.Add (ref rwlock, -RwWrite);
224                         writerDoneEvent.Set ();
225                 }
226
227                 public void EnterUpgradeableReadLock ()
228                 {
229                         TryEnterUpgradeableReadLock (-1);
230                 }
231
232                 //
233                 // Taking the Upgradable read lock is like taking a read lock
234                 // but we limit it to a single upgradable at a time.
235                 //
236                 public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
237                 {
238                         ThreadLockState ctstate = CurrentThreadState;
239
240                         if (CheckState (millisecondsTimeout, LockState.Upgradable)) {
241                                 ctstate.UpgradeableRecursiveCount++;
242                                 return true;
243                         }
244
245                         if (ctstate.LockState.Has (LockState.Read))
246                                 throw new LockRecursionException ("The current thread has already entered read mode");
247
248                         Stopwatch sw = Stopwatch.StartNew ();
249                         Interlocked.Increment (ref numUpgradeWaiters);
250
251                         while (!upgradableEvent.IsSet () || !upgradableTaken.TryRelaxedSet ()) {
252                                 if (millisecondsTimeout != -1 && sw.ElapsedMilliseconds > millisecondsTimeout) {
253                                         Interlocked.Decrement (ref numUpgradeWaiters);
254                                         return false;
255                                 }
256
257                                 upgradableEvent.Wait (ComputeTimeout (millisecondsTimeout, sw));
258                         }
259
260                         upgradableEvent.Reset ();
261
262                         if (TryEnterReadLock (ComputeTimeout (millisecondsTimeout, sw))) {
263                                 ctstate.LockState = LockState.Upgradable;
264                                 Interlocked.Decrement (ref numUpgradeWaiters);
265                                 ctstate.ReaderRecursiveCount--;
266                                 ctstate.UpgradeableRecursiveCount++;
267                                 return true;
268                         }
269
270                         upgradableTaken.Value = false;
271                         upgradableEvent.Set ();
272
273                         Interlocked.Decrement (ref numUpgradeWaiters);
274
275                         return false;
276                 }
277
278                 public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
279                 {
280                         return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
281                 }
282                
283                 public void ExitUpgradeableReadLock ()
284                 {
285                         ThreadLockState ctstate = CurrentThreadState;
286
287                         if (!ctstate.LockState.Has (LockState.Upgradable | LockState.Read))
288                                 throw new SynchronizationLockException ("The current thread has not entered the lock in upgradable mode");
289                         
290                         upgradableTaken.Value = false;
291                         upgradableEvent.Set ();
292
293                         ctstate.LockState ^= LockState.Upgradable;
294                         ctstate.UpgradeableRecursiveCount--;
295                         Interlocked.Add (ref rwlock, -RwRead);
296                 }
297
298                 public void Dispose ()
299                 {
300                         disposed = true;
301                 }
302
303                 public bool IsReadLockHeld {
304                         get {
305                                 return rwlock >= RwRead;
306                         }
307                 }
308                 
309                 public bool IsWriteLockHeld {
310                         get {
311                                 return (rwlock & RwWrite) > 0;
312                         }
313                 }
314                 
315                 public bool IsUpgradeableReadLockHeld {
316                         get {
317                                 return upgradableTaken.Value;
318                         }
319                 }
320
321                 public int CurrentReadCount {
322                         get {
323                                 return (rwlock >> RwReadBit) - (IsUpgradeableReadLockHeld ? 1 : 0);
324                         }
325                 }
326                 
327                 public int RecursiveReadCount {
328                         get {
329                                 return CurrentThreadState.ReaderRecursiveCount;
330                         }
331                 }
332
333                 public int RecursiveUpgradeCount {
334                         get {
335                                 return CurrentThreadState.UpgradeableRecursiveCount;
336                         }
337                 }
338
339                 public int RecursiveWriteCount {
340                         get {
341                                 return CurrentThreadState.WriterRecursiveCount;
342                         }
343                 }
344
345                 public int WaitingReadCount {
346                         get {
347                                 return numReadWaiters;
348                         }
349                 }
350
351                 public int WaitingUpgradeCount {
352                         get {
353                                 return numUpgradeWaiters;
354                         }
355                 }
356
357                 public int WaitingWriteCount {
358                         get {
359                                 return numWriteWaiters;
360                         }
361                 }
362
363                 public LockRecursionPolicy RecursionPolicy {
364                         get {
365                                 return recursionPolicy;
366                         }
367                 }
368
369                 LockState CurrentLockState {
370                         get {
371                                 return CurrentThreadState.LockState;
372                         }
373                         set {
374                                 CurrentThreadState.LockState = value;
375                         }
376                 }
377
378                 ThreadLockState CurrentThreadState {
379                         get {
380                                 if (currentThreadState == null)
381                                         currentThreadState = new Dictionary<int, ThreadLockState> ();
382
383                                 ThreadLockState state;
384                                 if (!currentThreadState.TryGetValue (id, out state))
385                                         currentThreadState[id] = state = new ThreadLockState ();
386
387                                 return state;
388                         }
389                 }
390
391                 bool CheckState (int millisecondsTimeout, LockState validState)
392                 {
393                         if (disposed)
394                                 throw new ObjectDisposedException ("ReaderWriterLockSlim");
395
396                         if (millisecondsTimeout < Timeout.Infinite)
397                                 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
398
399                         // Detect and prevent recursion
400                         LockState ctstate = CurrentLockState;
401
402                         if (recursionPolicy == LockRecursionPolicy.NoRecursion)
403                                 if ((ctstate != LockState.None && ctstate != LockState.Upgradable)
404                                     || (ctstate == LockState.Upgradable && validState == LockState.Upgradable))
405                                         throw new LockRecursionException ("The current thread has already a lock and recursion isn't supported");
406
407                         // If we already had right lock state, just return
408                         if (ctstate.Has (validState))
409                                 return true;
410
411                         CheckRecursionAuthorization (ctstate, validState);
412
413                         return false;
414                 }
415
416                 static void CheckRecursionAuthorization (LockState ctstate, LockState desiredState)
417                 {
418                         // In read mode you can just enter Read recursively
419                         if (ctstate == LockState.Read)
420                                 throw new LockRecursionException ();                            
421                 }
422
423                 static int CheckTimeout (TimeSpan timeout)
424                 {
425                         try {
426                                 return checked ((int)timeout.TotalMilliseconds);
427                         } catch (System.OverflowException) {
428                                 throw new ArgumentOutOfRangeException ("timeout");
429                         }
430                 }
431
432                 static int ComputeTimeout (int millisecondsTimeout, Stopwatch sw)
433                 {
434                         return millisecondsTimeout == -1 ? -1 : (int)Math.Max (sw.ElapsedMilliseconds - millisecondsTimeout, 1);
435                 }
436         }
437 }