/*
Copyright 2008 The 'A Concurrent Hashtable' development team
(http://www.codeplex.com/CH/People/ProjectPeople.aspx)
This library is licensed under the GNU Library General Public License (LGPL). You should
have received a copy of the license along with the source code. If not, an online copy
of the license can be found at http://www.codeplex.com/CH/license.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
namespace Orvid.Concurrent.Threading
{
///
/// Tiny spin lock that allows multiple readers simultanously and 1 writer exclusively
///
#if !SILVERLIGHT
[Serializable]
#endif
public struct TinyReaderWriterLock
{
#if !SILVERLIGHT
[NonSerialized]
#endif
Int32 _Bits;
const int ReadersInRegionOffset = 0;
const int ReadersInRegionsMask = 255;
const int ReadersWaitingOffset = 8;
const int ReadersWaitingMask = 255;
const int WriterInRegionOffset = 16;
const int WritersWaitingOffset = 17;
const int WritersWaitingMask = 15;
const int BiasOffset = 21;
const int BiasMask = 3;
enum Bias { None = 0, Readers = 1, Writers = 2 };
struct Data
{
public int _ReadersInRegion;
public int _ReadersWaiting;
public bool _WriterInRegion;
public int _WritersWaiting;
public Bias _Bias;
public Int32 _OldBits;
}
void GetData(out Data data)
{
Int32 bits = _Bits;
data._ReadersInRegion = bits & ReadersInRegionsMask ;
data._ReadersWaiting = (bits >> ReadersWaitingOffset) & ReadersWaitingMask;
data._WriterInRegion = ((bits >> WriterInRegionOffset) & 1) != 0;
data._WritersWaiting = (bits >> WritersWaitingOffset) & WritersWaitingMask;
data._Bias = (Bias)((bits >> BiasOffset) & BiasMask);
data._OldBits = bits;
}
bool SetData(ref Data data)
{
Int32 bits ;
bits =
data._ReadersInRegion
| (data._ReadersWaiting << ReadersWaitingOffset)
| ((data._WriterInRegion ? 1 : 0) << WriterInRegionOffset)
| (data._WritersWaiting << WritersWaitingOffset)
| ((int)data._Bias << BiasOffset);
return Interlocked.CompareExchange(ref _Bits, bits, data._OldBits) == data._OldBits;
}
///
/// Release a reader lock
///
public void ReleaseForReading()
{
//try shortcut first.
if (Interlocked.CompareExchange(ref _Bits, 0, 1) == 1)
return;
Data data;
do
{
GetData(out data);
#if DEBUG
if (data._ReadersInRegion == 0)
throw new InvalidOperationException("Mismatching Lock/Release for reading.");
//if (data._WriterInRegion)
// throw new InvalidOperationException("Unexpected writer in region.");
#endif
--data._ReadersInRegion;
if (data._ReadersInRegion == 0 && data._ReadersWaiting == 0)
data._Bias = data._WritersWaiting != 0 ? Bias.Writers : Bias.None;
}
while (!SetData(ref data));
}
///
/// Release a writer lock
///
public void ReleaseForWriting()
{
//try shortcut first.
if (Interlocked.CompareExchange(ref _Bits, 0, 1 << WriterInRegionOffset) == 1 << WriterInRegionOffset)
return;
Data data;
do
{
GetData(out data);
#if DEBUG
if (!data._WriterInRegion)
throw new InvalidOperationException("Mismatching Lock/Release for writing.");
//if (data._ReadersInRegion != 0)
// throw new InvalidOperationException("Unexpected reader in region.");
#endif
data._WriterInRegion = false;
if (data._WritersWaiting == 0)
data._Bias = data._ReadersWaiting != 0 ? Bias.Readers : Bias.None;
}
while (!SetData(ref data));
}
///
/// Aquire a reader lock. Wait until lock is aquired.
///
public void LockForReading()
{ LockForReading(true); }
///
/// Aquire a reader lock.
///
/// True if to wait until lock aquired, False to return immediately.
/// Boolean indicating if lock was successfuly aquired.
public bool LockForReading(bool wait)
{
//try shortcut first.
if (Interlocked.CompareExchange(ref _Bits, 1, 0) == 0)
return true;
bool waitingRegistered = false;
try
{
while (true)
{
bool retry = false;
Data data;
GetData(out data);
if (data._Bias != Bias.Writers)
{
if (data._ReadersInRegion < ReadersInRegionsMask && !data._WriterInRegion)
{
if (waitingRegistered)
{
data._Bias = Bias.Readers;
--data._ReadersWaiting;
++data._ReadersInRegion;
if (SetData(ref data))
{
waitingRegistered = false;
return true;
}
else
retry = true;
}
else if (data._WritersWaiting == 0)
{
data._Bias = Bias.Readers;
++data._ReadersInRegion;
if (SetData(ref data))
return true;
else
retry = true;
}
}
//sleep
}
else
{
if (!waitingRegistered && data._ReadersWaiting < ReadersWaitingMask && wait)
{
++data._ReadersWaiting;
if (SetData(ref data))
{
waitingRegistered = true;
//sleep
}
else
retry = true;
}
//sleep
}
if (!retry)
{
if (!wait)
return false;
System.Threading.Thread.Sleep(0);
}
}
}
finally
{
if (waitingRegistered)
{
//Thread aborted?
Data data;
do
{
GetData(out data);
--data._ReadersWaiting;
if (data._ReadersInRegion == 0 && data._ReadersWaiting == 0)
data._Bias = data._WritersWaiting != 0 ? Bias.Writers : Bias.None;
}
while (!SetData(ref data));
}
}
}
///
/// Aquire a writer lock. Wait until lock is aquired.
///
public void LockForWriting()
{ LockForWriting(true); }
///
/// Aquire a writer lock.
///
/// True if to wait until lock aquired, False to return immediately.
/// Boolean indicating if lock was successfuly aquired.
public bool LockForWriting(bool wait)
{
//try shortcut first.
if (Interlocked.CompareExchange(ref _Bits, 1 << WriterInRegionOffset, 0) == 0)
return true;
bool waitingRegistered = false;
try
{
while (true)
{
bool retry = false;
Data data;
GetData(out data);
if (data._Bias != Bias.Readers)
{
if (data._ReadersInRegion == 0 && !data._WriterInRegion)
{
if (waitingRegistered)
{
data._Bias = Bias.Writers;
--data._WritersWaiting;
data._WriterInRegion = true;
if (SetData(ref data))
{
waitingRegistered = false;
return true;
}
else
retry = true;
}
else if (data._ReadersWaiting == 0)
{
data._Bias = Bias.Writers;
data._WriterInRegion = true;
if (SetData(ref data))
return true;
else
retry = true;
}
}
//sleep
}
else
{
if (!waitingRegistered && data._WritersWaiting < WritersWaitingMask && wait)
{
++data._WritersWaiting;
if (SetData(ref data))
{
waitingRegistered = true;
//sleep
}
else
retry = true;
}
//sleep
}
if (!retry)
{
if (!wait)
return false;
System.Threading.Thread.Sleep(0);
}
}
}
finally
{
if (waitingRegistered)
{
//Thread aborted?
Data data;
do
{
GetData(out data);
--data._WritersWaiting;
if (!data._WriterInRegion && data._WritersWaiting == 0)
data._Bias = data._ReadersWaiting != 0 ? Bias.Readers : Bias.None;
}
while (!SetData(ref data));
}
}
}
}
}