Skip to content

Instantly share code, notes, and snippets.

@nathanpjones
Last active August 6, 2018 13:54
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nathanpjones/64c139ba4a909d30940a to your computer and use it in GitHub Desktop.
Save nathanpjones/64c139ba4a909d30940a to your computer and use it in GitHub Desktop.
Monitored Lock
/*
* This is an example console application to illustrate the usage
* of the MonitoredLock class and technique.
*/
using System;
using System.Diagnostics;
using System.Threading;
namespace MonitoredLock
{
public static class Example
{
private static object _lockObj = new object();
public static object LockObj
{
get { return _lockObj;}
}
public static void OtherStuff()
{
Thread.Sleep(1000);
using (new MonitoredLock(_lockObj, "LockObj"))
{
Console.WriteLine();
Console.WriteLine("** Entered other thread's locked statement");
Console.WriteLine();
}
Thread.Sleep(1000);
using (new MonitoredLock(_lockObj, "LockObj"))
{
Console.WriteLine();
Console.WriteLine("** Entered other thread's locked statement AGAIN");
Console.WriteLine();
}
}
public static void Main()
{
//Trace.Listeners.Add(new ConsoleTraceListener());
//Trace.Indent();
Console.WriteLine("** Started!");
Console.WriteLine();
var thread = new Thread(OtherStuff)
{
Name = "Other Thread",
IsBackground = false
};
thread.Start();
try
{
using (MonitoredLock.From(() => LockObj))
{
Console.WriteLine();
Console.WriteLine("** Starting body of Main lock");
Console.WriteLine();
Thread.Sleep(1500);
using (MonitoredLock.From(() => LockObj))
{
Console.WriteLine("*** Nested lock!");
}
Thread.Sleep(1500);
Console.WriteLine();
Console.WriteLine("** Ending body of Main lock");
Console.WriteLine();
}
Thread.Sleep(500); // allow other thread to lock
using (MonitoredLock.From(() => LockObj))
{
Console.WriteLine();
Console.WriteLine("** Starting body of SECOND Main lock");
Console.WriteLine();
Thread.Sleep(1500);
throw new Exception();
}
}
catch (Exception e)
{
Console.WriteLine("** Caught exception: " + e);
}
Console.ReadKey(true);
}
}
}
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace MonitoredLock
{
/// <summary>
/// Used to simulate syntax of 'lock' statement but with logging of
/// entry and exit of the lock. Intended to be used with the
/// 'using' statement.
/// </summary>
[DebuggerStepThrough]
public class MonitoredLock : IDisposable
{
private readonly static ConditionalWeakTable<object, Counter> LockDepthCounter = new ConditionalWeakTable<object, Counter>();
private readonly string _callerFilePath;
private readonly int _callerLine;
private readonly string _callerName;
private Counter _lockDepthCounter;
private readonly string _lockName;
private readonly object _lockObj;
private readonly bool _lockWasTaken;
private bool _disposed;
public MonitoredLock(object lockObj, string lockName, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerName = null, [CallerLineNumber] int callerLine = -1)
{
if (lockObj == null) throw new ArgumentNullException("lockObj");
_lockObj = lockObj;
_lockName = lockName ?? "<none>";
_callerFilePath = callerFilePath;
_callerName = callerName;
_callerLine = callerLine;
// Log that we're about to enter so it's clear who's waiting
// if we can't immediately take out the lock
LogMessage("WAIT ", false, false);
// Actually take out the lock
Monitor.Enter(_lockObj, ref _lockWasTaken);
// Increment depth counter and log entry
IncrementDepthCounter();
LogMessage("ENTER", false, false);
}
/// <summary>
/// <see cref="IDisposable"/> implementation.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose is where the lock is exited.
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
_disposed = true;
if (_lockWasTaken)
{
// Write exit message and decrement before exiting lock to ensure that
// it will output before the next entry into the lock.
LogMessage("EXIT ", true, disposing);
DecrementDepthCounter();
Monitor.Exit(_lockObj);
}
}
/// <summary>
/// Locates the counter for the lock object and increments it or
/// creates a new counter and initializes it to 1.
/// </summary>
[Conditional("TRACE")]
private void IncrementDepthCounter()
{
// Manage entry depth
if (LockDepthCounter.TryGetValue(_lockObj, out _lockDepthCounter))
{
// Increment existing counter
if (_lockWasTaken) _lockDepthCounter.Value++;
}
else
{
// Start new counter
_lockDepthCounter = new Counter() { Value = 1 };
LockDepthCounter.Add(_lockObj, _lockDepthCounter);
}
}
/// <summary>
/// Decrements a counter if there is one.
/// </summary>
[Conditional("TRACE")]
private void DecrementDepthCounter()
{
if (_lockDepthCounter == null) return;
// Decrement depth
_lockDepthCounter.Value--;
}
/// <summary>
/// Log the current action.
/// </summary>
/// <param name="action">Name of the action.</param>
/// <param name="isExit">Whether or not this is the exit message--slightly different formatting for this.</param>
/// <param name="isDisposing"></param>
[Conditional("TRACE")]
private void LogMessage(string action, bool isExit, bool isDisposing)
{
var currentThread = Thread.CurrentThread;
var exitInFinalizer = isExit && !isDisposing;
var msg = string.Format("{0} Lock '{1}'{2} on thread '{3}' [{4};{5}]{6} at {7} in {8}:line {9}{10}",
action,
_lockName,
null != _lockDepthCounter ? string.Format(" at depth {0}", _lockDepthCounter.Value) : null,
currentThread.Name ?? "<No Name>",
GetCurrentThreadId(),
currentThread.ManagedThreadId,
isExit ? " entered" : "",
_callerName,
_callerFilePath,
_callerLine,
exitInFinalizer ? " - WARNING: Lock was exited in finalizer!" : "");
if (exitInFinalizer)
Trace.TraceWarning(msg);
else
Trace.TraceInformation(msg);
}
/// <summary>
/// Create a <see cref="MonitoredLock"/> from an expression.
/// </summary>
/// <param name="memberExpression">Expression from which the object reference and name of the lock object are obtained.</param>
[SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
public static MonitoredLock From<T>(Expression<Func<T>> memberExpression, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerName = null, [CallerLineNumber] int callerLine = -1)
{
var expressionBody = (MemberExpression)memberExpression.Body;
return new MonitoredLock(memberExpression.Compile().Invoke(), expressionBody.Member.Name, callerFilePath, callerName, callerLine);
}
/// <summary>
/// Implemente a finalizer because want to make absolutely sure the lock has been cleared.
/// </summary>
~MonitoredLock()
{
Dispose(false);
}
/// <summary>
/// Have to call into OS to get real thread id. May not be desirable on non-Windows platforms.
/// </summary>
[DllImport("kernel32.dll")]
private static extern uint GetCurrentThreadId();
/// <summary>
/// This class only exists to provide a reference type for use with the ConditionalWeakTable above.
/// </summary>
private class Counter
{
public int Value { get; set; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment