Files
libremetaverse/libsecondlife/ReaderWriterLock.cs
John Hurliman 3013742668 * Increased SIMULATOR_TIMEOUT to 30 seconds
* Converted all timers to System.Threading timers to fix problems running in services and the CF
* UDPBase now uses our own ReaderWriterLock that is more efficient, and CF compatible
* Login uses a hand-created LoginProxy object instead of dynamically building the class with reflection .Emit()
* Replaced ParameterizedThreadStart calls with class-wide variables for CF compat.
* Removed transfer timeout code (irrelevant now that uploads go through CAPS)
* Added several new Helpers methods to wrap desktop and CF conditional code
* Replaced Monitor calls with AutoResetEvent in BlockingQueue
* InventoryNodeDictionary uses generics now
* Removed final lingering piece of XML serialization
* Added CookComputing.XmlRpc.CF.dll for the CF

git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1479 52acb1d6-8a22-11de-b505-999d5b087335
2007-11-06 09:26:10 +00:00

270 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace libsecondlife
{
/// <summary>
/// A reader-writer lock implementation that is intended to be simple, yet very
/// efficient. In particular only 1 interlocked operation is taken for any lock
/// operation (we use spin locks to achieve this). The spin lock is never held
/// for more than a few instructions (in particular, we never call event APIs
/// or in fact any non-trivial API while holding the spin lock).
///
/// Currently this ReaderWriterLock does not support recurision, however it is
/// not hard to add
/// </summary>
public class ReaderWriterLock
{
// Lock specifiation for myLock: This lock protects exactly the local fields associted
// instance of MyReaderWriterLock. It does NOT protect the memory associted with the
// the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
int myLock;
// Who owns the lock owners > 0 => readers
// owners = -1 means there is one writer. Owners must be >= -1.
int owners;
// These variables allow use to avoid Setting events (which is expensive) if we don't have to.
uint numWriteWaiters; // maximum number of threads that can be doing a WaitOne on the writeEvent
uint numReadWaiters; // maximum number of threads that can be doing a WaitOne on the readEvent
uint numUpgradeWaiters; // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1).
// conditions we wait on.
WaitHandle writeEvent; // threads waiting to aquire a write lock go here.
WaitHandle readEvent; // threads waiting to aquire a read lock go here (will be released in bulk)
WaitHandle upgradeEvent; // thread waiting to upgrade a read lock to a write lock go here (at most one)
public ReaderWriterLock()
{
// All state can start out zeroed.
}
public void AcquireReaderLock(int millisecondsTimeout)
{
EnterMyLock();
for (; ; )
{
// We can enter a read lock if there are only read-locks have been given out
// and a writer is not trying to get in.
if (owners >= 0 && numWriteWaiters == 0)
{
// Good case, there is no contention, we are basically done
owners++; // Indicate we have another reader
break;
}
// Drat, we need to wait. Mark that we have waiters and wait.
if (readEvent == null) // Create the needed event
{
LazyCreateEvent(ref readEvent, false);
continue; // since we left the lock, start over.
}
WaitOnEvent(readEvent, ref numReadWaiters, millisecondsTimeout);
}
ExitMyLock();
}
public void AcquireWriterLock(int millisecondsTimeout)
{
EnterMyLock();
for (; ; )
{
if (owners == 0)
{
// Good case, there is no contention, we are basically done
owners = -1; // indicate we have a writer.
break;
}
// Drat, we need to wait. Mark that we have waiters and wait.
if (writeEvent == null) // create the needed event.
{
LazyCreateEvent(ref writeEvent, true);
continue; // since we left the lock, start over.
}
WaitOnEvent(writeEvent, ref numWriteWaiters, millisecondsTimeout);
}
ExitMyLock();
}
public void UpgradeToWriterLock(int millisecondsTimeout)
{
EnterMyLock();
for (; ; )
{
Debug.Assert(owners > 0, "Upgrading when no reader lock held");
if (owners == 1)
{
// Good case, there is no contention, we are basically done
owners = -1; // inidicate we have a writer.
break;
}
// Drat, we need to wait. Mark that we have waiters and wait.
if (upgradeEvent == null) // Create the needed event
{
LazyCreateEvent(ref upgradeEvent, false);
continue; // since we left the lock, start over.
}
if (numUpgradeWaiters > 0)
{
ExitMyLock();
throw new ApplicationException("UpgradeToWriterLock already in process. Deadlock!");
}
WaitOnEvent(upgradeEvent, ref numUpgradeWaiters, millisecondsTimeout);
}
ExitMyLock();
}
public void ReleaseReaderLock()
{
EnterMyLock();
Debug.Assert(owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
--owners;
ExitAndWakeUpAppropriateWaiters();
}
public void ReleaseWriterLock()
{
EnterMyLock();
Debug.Assert(owners == -1, "Calling ReleaseWriterLock when no write lock is held");
Debug.Assert(numUpgradeWaiters > 0);
owners++;
ExitAndWakeUpAppropriateWaiters();
}
public void DowngradeToReaderLock()
{
EnterMyLock();
Debug.Assert(owners == -1, "Downgrading when no writer lock held");
owners = 1;
ExitAndWakeUpAppropriateWaiters();
}
/// <summary>
/// A routine for lazily creating a event outside the lock (so if errors
/// happen they are outside the lock and that we don't do much work
/// while holding a spin lock). If all goes well, reenter the lock and
/// set 'waitEvent'
/// </summary>
private void LazyCreateEvent(ref WaitHandle waitEvent, bool makeAutoResetEvent)
{
Debug.Assert(MyLockHeld);
Debug.Assert(waitEvent == null);
ExitMyLock();
WaitHandle newEvent;
if (makeAutoResetEvent)
newEvent = new AutoResetEvent(false);
else
newEvent = new ManualResetEvent(false);
EnterMyLock();
if (waitEvent == null) // maybe someone snuck in.
waitEvent = newEvent;
}
/// <summary>
/// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.
/// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
/// </summary>
private void WaitOnEvent(WaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
{
Debug.Assert(MyLockHeld);
if (waitEvent is AutoResetEvent)
((AutoResetEvent)waitEvent).Reset();
else
((ManualResetEvent)waitEvent).Reset();
numWaiters++;
bool waitSuccessful = false;
ExitMyLock(); // Do the wait outside of any lock
try
{
if (!waitEvent.WaitOne(millisecondsTimeout, false))
throw new ApplicationException("ReaderWriterLock timeout expired");
waitSuccessful = true;
}
finally
{
EnterMyLock();
--numWaiters;
if (!waitSuccessful) // We are going to throw for some reason. Exit myLock.
ExitMyLock();
}
}
/// <summary>
/// Determines the appropriate events to set, leaves the locks, and sets the events.
/// </summary>
private void ExitAndWakeUpAppropriateWaiters()
{
Debug.Assert(MyLockHeld);
if (owners == 0 && numWriteWaiters > 0)
{
ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
// release one writer
if (writeEvent is AutoResetEvent)
((AutoResetEvent)writeEvent).Set();
else
((ManualResetEvent)writeEvent).Set();
}
else if (owners == 1 && numUpgradeWaiters != 0)
{
ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
// release all upgraders (however there can be at most one).
// two threads upgrading is a guarenteed deadlock, so we throw in that case
if (upgradeEvent is AutoResetEvent)
((AutoResetEvent)upgradeEvent).Set();
else
((ManualResetEvent)upgradeEvent).Set();
}
else if (owners >= 0 && numReadWaiters != 0)
{
ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
// release all readers
if (readEvent is AutoResetEvent)
((AutoResetEvent)readEvent).Set();
else
((ManualResetEvent)readEvent).Set();
}
else
ExitMyLock();
}
private void EnterMyLock()
{
if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
EnterMyLockSpin();
}
private void EnterMyLockSpin()
{
for (int i = 0; ; i++)
{
Thread.Sleep(0); // Give up my quantum.
if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
return;
}
}
private void ExitMyLock()
{
Debug.Assert(myLock != 0, "Exiting spin lock that is not held");
myLock = 0;
}
private bool MyLockHeld { get { return myLock != 0; } }
};
}