// 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);
}
}
}
}
- 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) {
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
}
}
{
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);
}
}
{
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;
}
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;