#region Copyright 2011-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace CSharpTest.Net.Synchronization
{
///
/// Creates a tracking/assertion wrapper around an implementation of an ILockStrategy to verify lock state before
/// and after acquisition and release of both reader and writer locks.
///
public class DebugLocking : DebugLocking
where T : ILockStrategy, new()
{
/// Constructs the lock tracking object
public DebugLocking() : base(new T())
{ }
/// Constructs the lock tracking object
public DebugLocking(bool captureStack, int limitTimeout, int limitNestedReaders, bool concurrentReads, int limitNestedWriters)
: base(new T(), captureStack, limitTimeout, limitNestedReaders, concurrentReads, limitNestedWriters)
{ }
}
///
/// Creates a tracking/assertion wrapper around an implementation of an ILockStrategy to verify lock state before
/// and after acquisition and release of both reader and writer locks.
///
public class DebugLocking : ILockStrategy
{
bool _disposed;
readonly bool _captureStack;
readonly int _limitTimeout;
readonly int _limitNestedReaders;
readonly bool _concurrentReads;
readonly int _limitNestedWriters;
readonly ILockStrategy _lock;
DebugLockTracker _writer;
readonly Dictionary _readers;
/// Constructs the lock tracking object
public DebugLocking(ILockStrategy lck)
: this(lck, false, 30000, 0, false, 0)
{ }
/// Constructs the lock tracking object
public DebugLocking(ILockStrategy lck, bool captureStack, int limitTimeout, int limitNestedReaders, bool concurrentReads, int limitNestedWriters)
{
_captureStack = captureStack;
_limitTimeout = limitTimeout < 0 ? int.MaxValue : limitTimeout;
_limitNestedReaders = limitNestedReaders;
_concurrentReads = concurrentReads;
_limitNestedWriters = limitNestedWriters;
_lock = Check.NotNull(lck);
_readers = new Dictionary();
}
/// Capture the stack on every lock aquisition and release
public bool CaptureStack { get { return _captureStack; } }
/// Returns the highest number of concurrent reads
public int MaxReaderCount;
/// Returns the highest number of concurrent writes (aka max recursive count)
public int MaxWriterCount;
/// Returns the total number of current readers for all threads
public int CurrentReaderCount;
/// Returns the total number of current writers for all threads
public int CurrentWriterCount;
/// Returns the total number of read locks acquired
public int TotalReaderCount;
/// Returns the total number of write locks acquired
public int TotalWriterCount;
/// Returns the total number of current readers for this thread
public int LocalReaderCount
{
get
{
DebugLockTracker reader;
lock (_readers)
return _readers.TryGetValue(Thread.CurrentThread, out reader) ? reader.Count : 0;
}
}
/// Returns the total number of current writers for this thread
public int LocalWriterCount
{
get
{
DebugLockTracker writer = _writer;
return writer != null && ReferenceEquals(writer.Owner, Thread.CurrentThread) ? writer.Count : 0;
}
}
/// Changes every time a write lock is aquired. If WriteVersion == 0, no write locks have been issued.
public int WriteVersion { get { return _lock.WriteVersion; } }
/// Disposes of this lock
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_lock.Dispose();
DebugAssertionFailedException.Assert(CurrentReaderCount == 0, "Lock disposed with active readers.");
DebugAssertionFailedException.Assert(CurrentWriterCount == 0, "Lock disposed with active writers.");
}
}
private void AssertValid()
{
if (_disposed)
throw new ObjectDisposedException(String.Format("{0}({1})", GetType(), _lock.GetType()));
}
private int MaxTimeout(int timeout) { return Math.Min(_limitTimeout, timeout < 0 ? int.MaxValue : timeout); }
private static void AddCount(ref int maxValue, ref int currentValue, ref int totalValue)
{
Interlocked.Increment(ref totalValue);
int newMax = Interlocked.Increment(ref currentValue);
int oldMax;
while (newMax > (oldMax = maxValue))
Interlocked.CompareExchange(ref maxValue, newMax, oldMax);
}
///
/// Returns true if the lock was successfully obtained within the timeout specified
///
public bool TryRead(int timeout)
{
AssertValid();
Thread thread = Thread.CurrentThread;
int myWriteCount = 0;
DebugLockTracker reader, writer = _writer;
lock (_readers)
if (!_readers.TryGetValue(thread, out reader))
_readers.Add(thread, reader = new DebugLockTracker(thread));
int myReadCount = reader.Count;
if (writer != null && ReferenceEquals(writer.Owner, thread))
myWriteCount = writer.Count;
DebugAssertionFailedException.Assert((myReadCount + myWriteCount) <= _limitNestedReaders, "Current thread already holds max read locks.");
if (!_lock.TryRead(MaxTimeout(timeout)))
{
DebugAssertionFailedException.Assert(timeout < _limitTimeout, "Possible dead-lock in read lock, timeout limit reached.");
return false;
}
writer = _writer;
DebugAssertionFailedException.Assert(_concurrentReads || (writer == null || myWriteCount > 0), "Read lock acquired while writer lock exists.");
reader.AddLock(CaptureStack);
AddCount(ref MaxReaderCount, ref CurrentReaderCount, ref TotalReaderCount);
return true;
}
///
/// Releases a read lock
///
public void ReleaseRead()
{
AssertValid();
Thread thread = Thread.CurrentThread;
DebugLockTracker reader;
lock (_readers)
DebugAssertionFailedException.Assert(_readers.TryGetValue(thread, out reader) && reader.Count > 0, "Unable to release an unacquired read lock.");
DebugLockTracker writer = _writer;
DebugAssertionFailedException.Assert(_concurrentReads || writer == null || ReferenceEquals(writer.Owner, thread), "Read lock release while writer lock exists.");
_lock.ReleaseRead();
reader.ReleaseLock(CaptureStack);
Interlocked.Decrement(ref CurrentReaderCount);
}
///
/// Returns true if the lock was successfully obtained within the timeout specified
///
public bool TryWrite(int timeout)
{
AssertValid();
Thread thread = Thread.CurrentThread;
int myWriteCount = 0, myReadCount = 0;
DebugLockTracker reader, writer = _writer;
lock (_readers)
if (_readers.TryGetValue(thread, out reader))
myReadCount = reader.Count;
if (writer != null && ReferenceEquals(writer.Owner, thread))
myWriteCount = writer.Count;
DebugAssertionFailedException.Assert(myWriteCount > 0 || myReadCount == 0, "Potential dead-lock in acquire writer while reading.");
DebugAssertionFailedException.Assert(myWriteCount <= _limitNestedWriters, "Current thread already holds max write locks.");
if (!_lock.TryWrite(MaxTimeout(timeout)))
{
DebugAssertionFailedException.Assert(timeout < _limitTimeout, "Possible dead-lock in write lock, timeout limit reached.");
return false;
}
DebugAssertionFailedException.Assert(myWriteCount > 0 || _writer == null, "Write lock acquired while write lock exists.");
DebugAssertionFailedException.Assert(_concurrentReads || myReadCount == CurrentReaderCount, "Write lock acquired while reader lock exists.");
_writer = writer = _writer ?? new DebugLockTracker(thread);
writer.AddLock(CaptureStack);
AddCount(ref MaxWriterCount, ref CurrentWriterCount, ref TotalWriterCount);
return true;
}
///
/// Releases a writer lock
///
public void ReleaseWrite()
{
AssertValid();
Thread thread = Thread.CurrentThread;
DebugLockTracker writer = _writer;
DebugAssertionFailedException.Assert(writer != null && ReferenceEquals(writer.Owner, thread) && writer.Count > 0, "Unable to release an unacquired write lock.");
if (writer != null)
{
writer.ReleaseLock(CaptureStack);
if(writer.Count == 0)
_writer = null;
}
_lock.ReleaseWrite();
Interlocked.Decrement(ref CurrentWriterCount);
}
class DebugLockTracker
{
const int MaxDepth = 64;
public int Count;
public readonly Thread Owner;
readonly StackTrace[] _acquiredFrom;
readonly StackTrace[] _releasedFrom;
public DebugLockTracker(Thread owner)
{
Owner = owner;
Count = 0;
_acquiredFrom = new StackTrace[MaxDepth];
_releasedFrom = new StackTrace[MaxDepth];
}
public void AddLock(bool captureStack)
{
_acquiredFrom[Count] = captureStack ? new StackTrace(2, false) : null;
_releasedFrom[Count] = null;
Count++;
}
public void ReleaseLock(bool captureStack)
{
Count--;
_releasedFrom[Count] = captureStack ? new StackTrace(2, false) : null;
}
}
#region ILockStrategy Members
///
/// Returns a reader lock that can be elevated to a write lock
///
///
public ReadLock Read(int timeout) { return ReadLock.Acquire(this, timeout); }
///
/// Returns a reader lock that can be elevated to a write lock
///
public ReadLock Read() { return ReadLock.Acquire(this, -1); }
///
/// Returns a read and write lock
///
///
public WriteLock Write(int timeout) { return WriteLock.Acquire(this, timeout); }
///
/// Returns a read and write lock
///
public WriteLock Write() { return WriteLock.Acquire(this, -1); }
#endregion
}
}