I wrote a fairly trivial wrapper around ReaderWriterLockSlim
:
class SimpleReaderWriterLock
{
private class Guard : IDisposable
{
public Guard(Action action)
{
_Action = action;
}
public void Dispose()
{
_Action?.Invoke();
_Action = null;
}
private Action _Action;
}
private readonly ReaderWriterLockSlim _Lock
= new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
public IDisposable ReadLocked()
{
_Lock.EnterReadLock();
return new Guard(_Lock.ExitReadLock);
}
public IDisposable WriteLocked()
{
_Lock.EnterWriteLock();
return new Guard(_Lock.ExitWriteLock);
}
public IDisposable UpgradableReadLocked()
{
_Lock.EnterUpgradeableReadLock();
return new Guard(_Lock.ExitUpgradeableReadLock);
}
}
(This is probably not the most efficient thing in the world, so I am interested in suggested improvements to this class as well.)
It is used like so:
using (_Lock.ReadLocked())
{
// protected code
}
(There are a significant number of reads happening very frequently, and almost never any writes.)
This always seems to work as expected in Release mode and in production. However in Debug mode and in the debugger, very occasionally the process deadlocks in a peculiar state -- it has called EnterReadLock
, the lock itself is not held by anything (the owner is 0, the properties that report whether it has any readers/writers/waiters say not, etc) but the spin lock inside is locked, and it's endlessly spinning there.
I don't know what triggers this, except that it seems to happen more often if I'm stopping at breakpoints and single-stepping (in completely unrelated code).
If I manually toggle the spinlock _isLocked
field back to 0, then the process resumes and everything seems to work as expected afterwards.
Is there something wrong with the code or with the lock itself? Is the debugger doing something to accidentally provoke deadlocking the spinlock? (I'm using .NET 4.6.2.)
I've read an article that indicates that ThreadAbortException
can be a problem for these locks -- and my code does have calls to Abort()
in some places -- but I don't think those involve code which calls into this locked code (though I could be mistaken) and if the problem were that the lock had been acquired and never released then it should appear differently than what I'm seeing. (Though as an aside, the framework docs specifically ban acquiring a lock in a constrained region, as encouraged in that article.)
I can change the code to avoid the lock indirection, but aren't using
guards the recommended practice in general?