In our application we have data which needs to be shared between multiple threads. Now and then we ran into a race condition and we could not figure out where that was. So I have implemented a deadlock watchdog.
It is supposed to be used like this (example):
public bool GetFromElementExistsCache(string id)
{
using (new Lock(_syncElementExistsCache))
{
if (_elementExistsCache.ContainsKey(id))
{
return _elementExistsCache[id];
}
else
{
return false;
}
}
}
The implementation looks like this:
public sealed class Lock : IDisposable
{
private static readonly object CACHE_LOCK = new object();
private static readonly LogService LOG = new LogService(typeof(Lock));
private readonly object _lockObject;
private readonly bool _lockAcquired;
private static readonly List<LockInformation> LOCK_OBJECTS = new List<LockInformation>();
public Lock(object lockObject)
{
_lockObject = lockObject;
_lockAcquired = false;
UpdateLockInfo(LockInformation.LockRequest.Reqested);
Monitor.Enter(_lockObject, ref _lockAcquired);
if (!_lockAcquired)
{
LOG.Warn($"Could not get lock, possible deadlock detected for lock object {_lockObject.GetHashCode()}!");
}
UpdateLockInfo(LockInformation.LockRequest.Locked);
}
private void UpdateLockInfo(LockInformation.LockRequest lockStatus)
{
lock (CACHE_LOCK)
{
try
{
var lockExists = false;
LockInformation lockInfo = null;
foreach (var li in LOCK_OBJECTS)
{
if (li.LockObject == _lockObject && li.Thread == Thread.CurrentThread)
{
lockInfo = li;
}
if (li.LockStatus != LockInformation.LockRequest.Released)
{
lockExists = true;
}
}
if (!lockExists)
{
LOCK_OBJECTS.Clear();
}
// the current thread is allowed to update the status if it has created the lock entry
if (lockInfo != null)
{
if (lockStatus == LockInformation.LockRequest.Released)
{
LOCK_OBJECTS.Remove(lockInfo);
}
else if (lockInfo.LockStatus == LockInformation.LockRequest.Released || lockInfo.Thread == Thread.CurrentThread)
{
lockInfo.LockStatus = lockStatus;
lockInfo.Thread = Thread.CurrentThread;
}
}
else
{
LOCK_OBJECTS.Add(new LockInformation
{
LockStatus = lockStatus,
LockObject = _lockObject,
Thread = Thread.CurrentThread
});
}
}
catch (Exception e)
{
LOG.Warn("Error during updating lock information!", e);
}
}
}
public void Dispose()
{
if (_lockAcquired)
{
Monitor.Exit(_lockObject);
UpdateLockInfo(LockInformation.LockRequest.Released);
}
}
}
The class which holds the lock information
internal class LockInformation
{
public enum LockRequest
{
Reqested,
Locked,
Released
}
public object LockObject { get; set; }
public LockRequest LockStatus { get; set; }
public Thread Thread { get; set; }
public override bool Equals(object obj)
{
var lockInformation = obj as LockInformation;
if (lockInformation != null)
{
return lockInformation.LockObject == LockObject;
}
return false;
}
protected bool Equals(LockInformation lockInfo)
{
if (lockInfo == null)
{
return false;
}
return lockInfo.LockObject == LockObject;
}
public override int GetHashCode()
{
unchecked
{
int hashCode = LockObject?.GetHashCode() ?? 0;
hashCode = (hashCode * 397) ^ LockStatus.GetHashCode();
hashCode = (hashCode * 397) ^ (Thread?.GetHashCode() ?? 0);
return hashCode;
}
}
public override string ToString()
{
var threadName = Thread.Name;
if (string.IsNullOrEmpty(threadName))
{
threadName = "null";
}
return $"{LockObject.GetHashCode()} : {LockStatus} - {threadName}({Thread.ManagedThreadId})";
}
}
There is an additional class which takes care of the deadlock detection.
So far the implementation works just fine. But I was wondering if you guys see any problemens in for example performance or thread identification etc. since I do not have much experience there.
Also general tips for coding style etc. are much appreciated as well!