[corlib] Ensure Thread.ManagedThreadId is unique per thread.
[mono.git] / mcs / class / corlib / System.Threading / ReaderWriterLock.cs
old mode 100755 (executable)
new mode 100644 (file)
index 27f21d7..ebb718c
@@ -4,36 +4,75 @@
 // Author:
 //   Dick Porter (dick@ximian.com)
 //   Jackson Harper (jackson@ximian.com)
+//   Lluis Sanchez Gual (lluis@ximian.com)
 //
 // (C) Ximian, Inc.  http://www.ximian.com
 // (C) 2004 Novell, Inc (http://www.novell.com)
 //
 
+//
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Collections;
+using System.Runtime.InteropServices;
+using System.Runtime.ConstrainedExecution;
+
 
 namespace System.Threading
 {
-       public sealed class ReaderWriterLock
+       [ComVisible (true)]
+       public sealed class ReaderWriterLock: CriticalFinalizerObject
        {
                private int seq_num = 1;
-               private int state = 0;
-               private int readers = 0;
-               private bool writer_queued = false;
-               private int upgrade_id = -1;
+               private int state;
+               private int readers;
                private LockQueue writer_queue;
+               private Hashtable reader_locks;
+               private int writer_lock_owner;
 
                public ReaderWriterLock()
+               {
+                       writer_queue = new LockQueue (this);
+                       reader_locks = new Hashtable ();
+
+                       GC.SuppressFinalize (this);
+               }
+
+               ~ReaderWriterLock ()
                {
                }
 
                public bool IsReaderLockHeld {
+                       [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
                        get {
-                               lock (this) return (state > 0);
+                               lock (this) return reader_locks.ContainsKey (Thread.CurrentThreadId);
                        }
                }
 
                public bool IsWriterLockHeld {
+                       [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
                        get {
-                               lock (this) return (state < 0);
+                               lock (this) return (state < 0 && Thread.CurrentThreadId == writer_lock_owner);
                        }
                }
 
@@ -43,40 +82,84 @@ namespace System.Threading
                        }
                }
 
-               public void AcquireReaderLock(int millisecondsTimeout) {
+               public void AcquireReaderLock (int millisecondsTimeout) 
+               {
+                       AcquireReaderLock (millisecondsTimeout, 1);
+               }
+               
+               void AcquireReaderLock (int millisecondsTimeout, int initialLockCount) 
+               {
                        lock (this) {
-                               // Wait if there is a write lock
-                               readers++;
-                               if (state < 0 || writer_queued)
-                                       Monitor.Wait (this, millisecondsTimeout);
-                               readers--;
-                               state++;
+                               if (HasWriterLock ()) {
+                                       AcquireWriterLock (millisecondsTimeout, initialLockCount);
+                                       return;
+                               }
+                               
+                               object nlocks = reader_locks [Thread.CurrentThreadId];
+                               if (nlocks == null)
+                               {
+                                       // Not currently holding a reader lock
+                                       // Wait if there is a write lock
+                                       readers++;
+                                       try {
+                                               if (state < 0 || !writer_queue.IsEmpty) {
+                                                       do {
+                                                               if (!Monitor.Wait (this, millisecondsTimeout))
+                                                                       throw new ApplicationException ("Timeout expired");
+                                                       } while (state < 0);
+                                               }
+                                       }
+                                       finally {
+                                               readers--;
+                                       }
+                                       
+                                       reader_locks [Thread.CurrentThreadId] = initialLockCount;
+                                       state += initialLockCount;
+                               }
+                               else {
+                                       reader_locks [Thread.CurrentThreadId] = ((int)nlocks) + 1;
+                                       state++;
+                               }
                        }
                }
 
                public void AcquireReaderLock(TimeSpan timeout)
                {
                        int ms = CheckTimeout (timeout);
-                       AcquireReaderLock ((int) timeout.TotalMilliseconds);
+                       AcquireReaderLock (ms, 1);
                }
 
-               public void AcquireWriterLock(int millisecondsTimeout)
+               public void AcquireWriterLock (int millisecondsTimeout)
+               {
+                       AcquireWriterLock (millisecondsTimeout, 1);
+               }
+               
+               void AcquireWriterLock (int millisecondsTimeout, int initialLockCount)
                {
                        lock (this) {
-                               if (writer_queue == null)
-                                       writer_queue = new LockQueue (this);
-                               writer_queued = true;
-                               if (state != 0) // wait while there are reader locks or another writer lock
-                                       writer_queue.Wait (millisecondsTimeout);
-                               writer_queued = false;
-                               state = -1;
+                               if (HasWriterLock ()) {
+                                       state--;
+                                       return;
+                               }
+                               
+                               // wait while there are reader locks or another writer lock, or
+                               // other threads waiting for the writer lock
+                               if (state != 0 || !writer_queue.IsEmpty) {
+                                       do {
+                                               if (!writer_queue.Wait (millisecondsTimeout))
+                                                       throw new ApplicationException ("Timeout expired");
+                                       } while (state != 0);
+                               }
+
+                               state = -initialLockCount;
+                               writer_lock_owner = Thread.CurrentThreadId;
+                               seq_num++;
                        }
-                       Interlocked.Increment (ref seq_num);
                }
 
                public void AcquireWriterLock(TimeSpan timeout) {
                        int ms = CheckTimeout (timeout);
-                       AcquireWriterLock (ms);
+                       AcquireWriterLock (ms, 1);
                }
 
                public bool AnyWritersSince(int seqNum) {
@@ -88,10 +171,21 @@ namespace System.Threading
                public void DowngradeFromWriterLock(ref LockCookie lockCookie)
                {
                        lock (this) {
-                               state = 1;
-                               Monitor.Pulse (this);
-                               if (writer_queue != null)
-                                       writer_queue.Pulse ();
+                               if (!HasWriterLock())
+                                       throw new ApplicationException ("The thread does not have the writer lock.");
+
+                               if (lockCookie.WriterLocks != 0)
+                                       state++;
+                               else {
+                                       state = lockCookie.ReaderLocks;
+                                       reader_locks [Thread.CurrentThreadId] = state;
+                                       if (readers > 0) {
+                                               Monitor.PulseAll (this);
+                                       }
+                               }
+                               
+                               // MSDN: A thread does not block when downgrading from the writer lock, 
+                               // even if other threads are waiting for the writer lock
                        }
                }
 
@@ -99,44 +193,80 @@ namespace System.Threading
                {
                        LockCookie cookie;
                        lock (this) {
-                               cookie = new LockCookie (Thread.CurrentThreadId, state < 0, state > 0);
-                               ReleaseWriterLock ();
+                               cookie = GetLockCookie ();
+                               if (cookie.WriterLocks != 0)
+                                       ReleaseWriterLock (cookie.WriterLocks);
+                               else if (cookie.ReaderLocks != 0) {
+                                       ReleaseReaderLock (cookie.ReaderLocks, cookie.ReaderLocks);
+                               }
                        }
                        return cookie;
                }
-
+               
+               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
                public void ReleaseReaderLock()
                {
                        lock (this) {
-                               int min_state = (upgrade_id == -1 ? 0 : 1);
-                               if (upgrade_id != -1 && upgrade_id == Thread.CurrentThreadId) {
-                                       Monitor.Pulse (this);
-                                       writer_queue.Pulse ();
+                               if (HasWriterLock ()) {
+                                       ReleaseWriterLock ();
                                        return;
                                }
-                               if (--state == min_state && writer_queue != null)
-                                       writer_queue.Pulse ();
+                               else if (state > 0) {
+                                       object read_lock_count = reader_locks [Thread.CurrentThreadId];
+                                       if (read_lock_count != null) {
+                                               ReleaseReaderLock ((int)read_lock_count, 1);
+                                               return;
+                                       }
+                               }
+
+                               throw new ApplicationException ("The thread does not have any reader or writer locks.");
                        }
                }
 
+               void ReleaseReaderLock (int currentCount, int releaseCount)
+               {
+                       int new_count = currentCount - releaseCount;
+                       
+                       if (new_count == 0)
+                               reader_locks.Remove (Thread.CurrentThreadId);
+                       else
+                               reader_locks [Thread.CurrentThreadId] = new_count;
+                               
+                       state -= releaseCount;
+                       if (state == 0 && !writer_queue.IsEmpty)
+                               writer_queue.Pulse ();
+               }
+
+               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
                public void ReleaseWriterLock()
                {
                        lock (this) {
-                               state = 0;
-                               if (readers > 0)
-                                       Monitor.Pulse (this);
-                               else if (writer_queue != null)
-                                       writer_queue.Pulse ();
+                               if (!HasWriterLock())
+                                       throw new ApplicationException ("The thread does not have the writer lock.");
+                               
+                               ReleaseWriterLock (1);
                        }
                }
 
+               void ReleaseWriterLock (int releaseCount)
+               {
+                       state += releaseCount;
+                       if (state == 0) {
+                               if (readers > 0) {
+                                       Monitor.PulseAll (this);
+                               }
+                               else if (!writer_queue.IsEmpty)
+                                       writer_queue.Pulse ();
+                       }
+               }
+               
                public void RestoreLock(ref LockCookie lockCookie)
                {
                        lock (this) {
-                               if (lockCookie.IsWriter)
-                                       AcquireWriterLock (-1);
-                               else if (lockCookie.IsReader)
-                                       AcquireReaderLock (-1);
+                               if (lockCookie.WriterLocks != 0)
+                                       AcquireWriterLock (-1, lockCookie.WriterLocks);
+                               else if (lockCookie.ReaderLocks != 0)
+                                       AcquireReaderLock (-1, lockCookie.ReaderLocks);
                        }
                }
 
@@ -144,21 +274,18 @@ namespace System.Threading
                {
                        LockCookie cookie;
                        lock (this) {
-                               upgrade_id = Thread.CurrentThreadId;
-                               cookie = new LockCookie (upgrade_id, state < 0, state > 0);
-                               
-                               if (writer_queue == null)
-                                       writer_queue = new LockQueue (this);
-                               
-                               writer_queued = true;
-                               if (state != 1) // wait until there is only 1 reader lock
-                                       writer_queue.Wait (millisecondsTimeout);
-                               writer_queued = false;
-                               state = -1;
-                               upgrade_id = -1;
+                               cookie = GetLockCookie ();
+                               if (cookie.WriterLocks != 0) {
+                                       state--;
+                                       return cookie;
+                               }
                                
+                               if (cookie.ReaderLocks != 0)
+                                       ReleaseReaderLock (cookie.ReaderLocks, cookie.ReaderLocks);
                        }
-                       Interlocked.Increment (ref seq_num);
+                       
+                       // Don't do this inside the lock, since it can cause a deadlock.
+                       AcquireWriterLock (millisecondsTimeout);
                        return cookie;
                }
                
@@ -167,7 +294,25 @@ namespace System.Threading
                        int ms = CheckTimeout (timeout);
                        return UpgradeToWriterLock (ms);
                }
+               
+               LockCookie GetLockCookie ()
+               {
+                       LockCookie cookie = new LockCookie (Thread.CurrentThreadId);
+                       
+                       if (HasWriterLock())
+                               cookie.WriterLocks = -state;
+                       else {
+                               object locks = reader_locks [Thread.CurrentThreadId];
+                               if (locks != null) cookie.ReaderLocks = (int)locks;
+                       }
+                       return cookie;
+               }
 
+               bool HasWriterLock ()
+               {
+                       return (state < 0 && Thread.CurrentThreadId == writer_lock_owner);
+               }
+               
                private int CheckTimeout (TimeSpan timeout)
                {
                        int ms = (int) timeout.TotalMilliseconds;