New test.
[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 namespace System.Threading
39 {
40         public sealed class ReaderWriterLock
41         {
42                 private int seq_num = 1;
43                 private int state;
44                 private int readers;
45                 private LockQueue writer_queue;
46                 private Hashtable reader_locks;
47                 private int writer_lock_owner;
48
49                 public ReaderWriterLock()
50                 {
51                         writer_queue = new LockQueue (this);
52                         reader_locks = new Hashtable ();
53                 }
54
55                 public bool IsReaderLockHeld {
56                         get {
57                                 lock (this) return reader_locks.ContainsKey (Thread.CurrentThreadId);
58                         }
59                 }
60
61                 public bool IsWriterLockHeld {
62                         get {
63                                 lock (this) return (state < 0 && Thread.CurrentThreadId == writer_lock_owner);
64                         }
65                 }
66
67                 public int WriterSeqNum {
68                         get {
69                                 lock (this) return seq_num;
70                         }
71                 }
72
73                 public void AcquireReaderLock (int millisecondsTimeout) 
74                 {
75                         AcquireReaderLock (millisecondsTimeout, 1);
76                 }
77                 
78                 void AcquireReaderLock (int millisecondsTimeout, int initialLockCount) 
79                 {
80                         lock (this) {
81                                 if (HasWriterLock ()) {
82                                         AcquireWriterLock (millisecondsTimeout, initialLockCount);
83                                         return;
84                                 }
85                                 
86                                 object nlocks = reader_locks [Thread.CurrentThreadId];
87                                 if (nlocks == null)
88                                 {
89                                         // Not currently holding a reader lock
90                                         // Wait if there is a write lock
91                                         readers++;
92                                         try {
93                                                 if (state < 0 || !writer_queue.IsEmpty) {
94                                                         do {
95                                                                 if (!Monitor.Wait (this, millisecondsTimeout))
96                                                                         throw new ApplicationException ("Timeout expired");
97                                                         } while (state < 0);
98                                                 }
99                                         }
100                                         finally {
101                                                 readers--;
102                                         }
103                                         
104                                         reader_locks [Thread.CurrentThreadId] = initialLockCount;
105                                         state += initialLockCount;
106                                 }
107                                 else {
108                                         reader_locks [Thread.CurrentThreadId] = ((int)nlocks) + 1;
109                                         state++;
110                                 }
111                         }
112                 }
113
114                 public void AcquireReaderLock(TimeSpan timeout)
115                 {
116                         int ms = CheckTimeout (timeout);
117                         AcquireReaderLock (ms, 1);
118                 }
119
120                 public void AcquireWriterLock (int millisecondsTimeout)
121                 {
122                         AcquireWriterLock (millisecondsTimeout, 1);
123                 }
124                 
125                 void AcquireWriterLock (int millisecondsTimeout, int initialLockCount)
126                 {
127                         lock (this) {
128                                 if (HasWriterLock ()) {
129                                         state--;
130                                         return;
131                                 }
132                                 
133                                 // wait while there are reader locks or another writer lock, or
134                                 // other threads waiting for the writer lock
135                                 if (state != 0 || !writer_queue.IsEmpty) {
136                                         do {
137                                                 if (!writer_queue.Wait (millisecondsTimeout))
138                                                         throw new ApplicationException ("Timeout expired");
139                                         } while (state != 0);
140                                 }
141
142                                 state = -initialLockCount;
143                                 writer_lock_owner = Thread.CurrentThreadId;
144                                 seq_num++;
145                         }
146                 }
147
148                 public void AcquireWriterLock(TimeSpan timeout) {
149                         int ms = CheckTimeout (timeout);
150                         AcquireWriterLock (ms, 1);
151                 }
152
153                 public bool AnyWritersSince(int seqNum) {
154                         lock (this) {
155                                 return (this.seq_num > seqNum);
156                         }
157                 }
158
159                 public void DowngradeFromWriterLock(ref LockCookie lockCookie)
160                 {
161                         lock (this) {
162                                 if (!HasWriterLock())
163                                         throw new ApplicationException ("The thread does not have the writer lock.");
164                                 
165                                 state = lockCookie.ReaderLocks;
166                                 reader_locks [Thread.CurrentThreadId] = state;
167                                 if (readers > 0) {
168                                         Monitor.PulseAll (this);
169                                 }
170                                 
171                                 // MSDN: A thread does not block when downgrading from the writer lock, 
172                                 // even if other threads are waiting for the writer lock
173                         }
174                 }
175
176                 public LockCookie ReleaseLock()
177                 {
178                         LockCookie cookie;
179                         lock (this) {
180                                 cookie = GetLockCookie ();
181                                 if (cookie.WriterLocks != 0)
182                                         ReleaseWriterLock (cookie.WriterLocks);
183                                 else if (cookie.ReaderLocks != 0) {
184                                         ReleaseReaderLock (cookie.ReaderLocks, cookie.ReaderLocks);
185                                 }
186                         }
187                         return cookie;
188                 }
189                 
190                 public void ReleaseReaderLock()
191                 {
192                         lock (this) {
193                                 if (HasWriterLock ()) {
194                                         ReleaseWriterLock ();
195                                         return;
196                                 }
197                                 else if (state > 0) {
198                                         object read_lock_count = reader_locks [Thread.CurrentThreadId];
199                                         if (read_lock_count != null) {
200                                                 ReleaseReaderLock ((int)read_lock_count, 1);
201                                                 return;
202                                         }
203                                 }
204
205                                 throw new ApplicationException ("The thread does not have any reader or writer locks.");
206                         }
207                 }
208
209                 void ReleaseReaderLock (int currentCount, int releaseCount)
210                 {
211                         int new_count = currentCount - releaseCount;
212                         
213                         if (new_count == 0)
214                                 reader_locks.Remove (Thread.CurrentThreadId);
215                         else
216                                 reader_locks [Thread.CurrentThreadId] = new_count;
217                                 
218                         state -= releaseCount;
219                         if (state == 0 && !writer_queue.IsEmpty)
220                                 writer_queue.Pulse ();
221                 }
222
223                 public void ReleaseWriterLock()
224                 {
225                         lock (this) {
226                                 if (!HasWriterLock())
227                                         throw new ApplicationException ("The thread does not have the writer lock.");
228                                 
229                                 ReleaseWriterLock (1);
230                         }
231                 }
232
233                 void ReleaseWriterLock (int releaseCount)
234                 {
235                         state += releaseCount;
236                         if (state == 0) {
237                                 if (readers > 0) {
238                                         Monitor.PulseAll (this);
239                                 }
240                                 else if (!writer_queue.IsEmpty)
241                                         writer_queue.Pulse ();
242                         }
243                 }
244                 
245                 public void RestoreLock(ref LockCookie lockCookie)
246                 {
247                         lock (this) {
248                                 if (lockCookie.WriterLocks != 0)
249                                         AcquireWriterLock (-1, lockCookie.WriterLocks);
250                                 else if (lockCookie.ReaderLocks != 0)
251                                         AcquireReaderLock (-1, lockCookie.ReaderLocks);
252                         }
253                 }
254
255                 public LockCookie UpgradeToWriterLock(int millisecondsTimeout)
256                 {
257                         LockCookie cookie;
258                         lock (this) {
259                                 cookie = GetLockCookie ();
260                                 if (cookie.WriterLocks != 0) {
261                                         state--;
262                                         return cookie;
263                                 }
264                                 
265                                 if (cookie.ReaderLocks != 0)
266                                         ReleaseReaderLock (cookie.ReaderLocks, cookie.ReaderLocks);
267                         }
268                         
269                         // Don't do this inside the lock, since it can cause a deadlock.
270                         AcquireWriterLock (millisecondsTimeout);
271                         return cookie;
272                 }
273                 
274                 public LockCookie UpgradeToWriterLock(TimeSpan timeout)
275                 {
276                         int ms = CheckTimeout (timeout);
277                         return UpgradeToWriterLock (ms);
278                 }
279                 
280                 LockCookie GetLockCookie ()
281                 {
282                         LockCookie cookie = new LockCookie (Thread.CurrentThreadId);
283                         
284                         if (HasWriterLock())
285                                 cookie.WriterLocks = -state;
286                         else {
287                                 object locks = reader_locks [Thread.CurrentThreadId];
288                                 if (locks != null) cookie.ReaderLocks = (int)locks;
289                         }
290                         return cookie;
291                 }
292
293                 bool HasWriterLock ()
294                 {
295                         return (state < 0 && Thread.CurrentThreadId == writer_lock_owner);
296                 }
297                 
298                 private int CheckTimeout (TimeSpan timeout)
299                 {
300                         int ms = (int) timeout.TotalMilliseconds;
301
302                         if (ms < -1)
303                                 throw new ArgumentOutOfRangeException ("timeout",
304                                                 "Number must be either non-negative or -1");
305                         return ms;
306                 }
307         }
308 }
309