2007-05-02 Dick Porter <dick@ximian.com>
[mono.git] / mcs / class / corlib / System.Threading / ReaderWriterLock.cs
1 //
2 // System.Threading.ReaderWriterLock.cs
3 //
4 // Author:
5 //   Dick Porter (dick@ximian.com)
6 //   Jackson Harper (jackson@ximian.com)
7 //   Lluis Sanchez Gual (lluis@ximian.com)
8 //
9 // (C) Ximian, Inc.  http://www.ximian.com
10 // (C) 2004 Novell, Inc (http://www.novell.com)
11 //
12
13 //
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System.Collections;
37
38 #if NET_2_0
39 using System.Runtime.InteropServices;
40 using System.Runtime.ConstrainedExecution;
41 #endif
42
43 namespace System.Threading
44 {
45 #if NET_2_0
46         [ComVisible (true)]
47         public sealed class ReaderWriterLock: CriticalFinalizerObject
48 #else
49         public sealed class ReaderWriterLock
50 #endif
51         {
52                 private int seq_num = 1;
53                 private int state;
54                 private int readers;
55                 private LockQueue writer_queue;
56                 private Hashtable reader_locks;
57                 private int writer_lock_owner;
58
59                 public ReaderWriterLock()
60                 {
61                         writer_queue = new LockQueue (this);
62                         reader_locks = new Hashtable ();
63                 }
64
65                 public bool IsReaderLockHeld {
66 #if NET_2_0
67                         [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
68 #endif
69                         get {
70                                 lock (this) return reader_locks.ContainsKey (Thread.CurrentThreadId);
71                         }
72                 }
73
74                 public bool IsWriterLockHeld {
75 #if NET_2_0
76                         [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
77 #endif
78                         get {
79                                 lock (this) return (state < 0 && Thread.CurrentThreadId == writer_lock_owner);
80                         }
81                 }
82
83                 public int WriterSeqNum {
84                         get {
85                                 lock (this) return seq_num;
86                         }
87                 }
88
89                 public void AcquireReaderLock (int millisecondsTimeout) 
90                 {
91                         AcquireReaderLock (millisecondsTimeout, 1);
92                 }
93                 
94                 void AcquireReaderLock (int millisecondsTimeout, int initialLockCount) 
95                 {
96                         lock (this) {
97                                 if (HasWriterLock ()) {
98                                         AcquireWriterLock (millisecondsTimeout, initialLockCount);
99                                         return;
100                                 }
101                                 
102                                 object nlocks = reader_locks [Thread.CurrentThreadId];
103                                 if (nlocks == null)
104                                 {
105                                         // Not currently holding a reader lock
106                                         // Wait if there is a write lock
107                                         readers++;
108                                         try {
109                                                 if (state < 0 || !writer_queue.IsEmpty) {
110                                                         do {
111                                                                 if (!Monitor.Wait (this, millisecondsTimeout))
112                                                                         throw new ApplicationException ("Timeout expired");
113                                                         } while (state < 0);
114                                                 }
115                                         }
116                                         finally {
117                                                 readers--;
118                                         }
119                                         
120                                         reader_locks [Thread.CurrentThreadId] = initialLockCount;
121                                         state += initialLockCount;
122                                 }
123                                 else {
124                                         reader_locks [Thread.CurrentThreadId] = ((int)nlocks) + 1;
125                                         state++;
126                                 }
127                         }
128                 }
129
130                 public void AcquireReaderLock(TimeSpan timeout)
131                 {
132                         int ms = CheckTimeout (timeout);
133                         AcquireReaderLock (ms, 1);
134                 }
135
136                 public void AcquireWriterLock (int millisecondsTimeout)
137                 {
138                         AcquireWriterLock (millisecondsTimeout, 1);
139                 }
140                 
141                 void AcquireWriterLock (int millisecondsTimeout, int initialLockCount)
142                 {
143                         lock (this) {
144                                 if (HasWriterLock ()) {
145                                         state--;
146                                         return;
147                                 }
148                                 
149                                 // wait while there are reader locks or another writer lock, or
150                                 // other threads waiting for the writer lock
151                                 if (state != 0 || !writer_queue.IsEmpty) {
152                                         do {
153                                                 if (!writer_queue.Wait (millisecondsTimeout))
154                                                         throw new ApplicationException ("Timeout expired");
155                                         } while (state != 0);
156                                 }
157
158                                 state = -initialLockCount;
159                                 writer_lock_owner = Thread.CurrentThreadId;
160                                 seq_num++;
161                         }
162                 }
163
164                 public void AcquireWriterLock(TimeSpan timeout) {
165                         int ms = CheckTimeout (timeout);
166                         AcquireWriterLock (ms, 1);
167                 }
168
169                 public bool AnyWritersSince(int seqNum) {
170                         lock (this) {
171                                 return (this.seq_num > seqNum);
172                         }
173                 }
174
175                 public void DowngradeFromWriterLock(ref LockCookie lockCookie)
176                 {
177                         lock (this) {
178                                 if (!HasWriterLock())
179                                         throw new ApplicationException ("The thread does not have the writer lock.");
180                                 
181                                 state = lockCookie.ReaderLocks;
182                                 reader_locks [Thread.CurrentThreadId] = state;
183                                 if (readers > 0) {
184                                         Monitor.PulseAll (this);
185                                 }
186                                 
187                                 // MSDN: A thread does not block when downgrading from the writer lock, 
188                                 // even if other threads are waiting for the writer lock
189                         }
190                 }
191
192                 public LockCookie ReleaseLock()
193                 {
194                         LockCookie cookie;
195                         lock (this) {
196                                 cookie = GetLockCookie ();
197                                 if (cookie.WriterLocks != 0)
198                                         ReleaseWriterLock (cookie.WriterLocks);
199                                 else if (cookie.ReaderLocks != 0) {
200                                         ReleaseReaderLock (cookie.ReaderLocks, cookie.ReaderLocks);
201                                 }
202                         }
203                         return cookie;
204                 }
205                 
206 #if NET_2_0
207                 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
208 #endif
209                 public void ReleaseReaderLock()
210                 {
211                         lock (this) {
212                                 if (HasWriterLock ()) {
213                                         ReleaseWriterLock ();
214                                         return;
215                                 }
216                                 else if (state > 0) {
217                                         object read_lock_count = reader_locks [Thread.CurrentThreadId];
218                                         if (read_lock_count != null) {
219                                                 ReleaseReaderLock ((int)read_lock_count, 1);
220                                                 return;
221                                         }
222                                 }
223
224                                 throw new ApplicationException ("The thread does not have any reader or writer locks.");
225                         }
226                 }
227
228                 void ReleaseReaderLock (int currentCount, int releaseCount)
229                 {
230                         int new_count = currentCount - releaseCount;
231                         
232                         if (new_count == 0)
233                                 reader_locks.Remove (Thread.CurrentThreadId);
234                         else
235                                 reader_locks [Thread.CurrentThreadId] = new_count;
236                                 
237                         state -= releaseCount;
238                         if (state == 0 && !writer_queue.IsEmpty)
239                                 writer_queue.Pulse ();
240                 }
241
242 #if NET_2_0
243                 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
244 #endif
245                 public void ReleaseWriterLock()
246                 {
247                         lock (this) {
248                                 if (!HasWriterLock())
249                                         throw new ApplicationException ("The thread does not have the writer lock.");
250                                 
251                                 ReleaseWriterLock (1);
252                         }
253                 }
254
255                 void ReleaseWriterLock (int releaseCount)
256                 {
257                         state += releaseCount;
258                         if (state == 0) {
259                                 if (readers > 0) {
260                                         Monitor.PulseAll (this);
261                                 }
262                                 else if (!writer_queue.IsEmpty)
263                                         writer_queue.Pulse ();
264                         }
265                 }
266                 
267                 public void RestoreLock(ref LockCookie lockCookie)
268                 {
269                         lock (this) {
270                                 if (lockCookie.WriterLocks != 0)
271                                         AcquireWriterLock (-1, lockCookie.WriterLocks);
272                                 else if (lockCookie.ReaderLocks != 0)
273                                         AcquireReaderLock (-1, lockCookie.ReaderLocks);
274                         }
275                 }
276
277                 public LockCookie UpgradeToWriterLock(int millisecondsTimeout)
278                 {
279                         LockCookie cookie;
280                         lock (this) {
281                                 cookie = GetLockCookie ();
282                                 if (cookie.WriterLocks != 0) {
283                                         state--;
284                                         return cookie;
285                                 }
286                                 
287                                 if (cookie.ReaderLocks != 0)
288                                         ReleaseReaderLock (cookie.ReaderLocks, cookie.ReaderLocks);
289                         }
290                         
291                         // Don't do this inside the lock, since it can cause a deadlock.
292                         AcquireWriterLock (millisecondsTimeout);
293                         return cookie;
294                 }
295                 
296                 public LockCookie UpgradeToWriterLock(TimeSpan timeout)
297                 {
298                         int ms = CheckTimeout (timeout);
299                         return UpgradeToWriterLock (ms);
300                 }
301                 
302                 LockCookie GetLockCookie ()
303                 {
304                         LockCookie cookie = new LockCookie (Thread.CurrentThreadId);
305                         
306                         if (HasWriterLock())
307                                 cookie.WriterLocks = -state;
308                         else {
309                                 object locks = reader_locks [Thread.CurrentThreadId];
310                                 if (locks != null) cookie.ReaderLocks = (int)locks;
311                         }
312                         return cookie;
313                 }
314
315                 bool HasWriterLock ()
316                 {
317                         return (state < 0 && Thread.CurrentThreadId == writer_lock_owner);
318                 }
319                 
320                 private int CheckTimeout (TimeSpan timeout)
321                 {
322                         int ms = (int) timeout.TotalMilliseconds;
323
324                         if (ms < -1)
325                                 throw new ArgumentOutOfRangeException ("timeout",
326                                                 "Number must be either non-negative or -1");
327                         return ms;
328                 }
329         }
330 }
331