public void ReleaseReaderLock()
{
// Check if the thread has writer lock
if (_writerID == GetCurrentThreadID())
{
ReleaseWriterLock();
return;
}
ThreadLocalLockEntry threadLocalLockEntry = ThreadLocalLockEntry.GetCurrent(_lockID);
if (threadLocalLockEntry == null)
{
throw GetNotOwnerException();
}
Debug.Assert((_state & LockStates.Writer) == 0);
Debug.Assert((_state & LockStates.ReadersMask) != 0);
Debug.Assert(threadLocalLockEntry._readerLevel > 0);
--threadLocalLockEntry._readerLevel;
if (threadLocalLockEntry._readerLevel > 0)
{
return;
}
// Not a reader any more
bool isLastReader;
bool cacheEvents;
AutoResetEvent writerEvent = null;
ManualResetEventSlim readerEvent = null;
int currentState = _state;
int knownState;
do
{
isLastReader = false;
cacheEvents = false;
knownState = currentState;
int modifyState = -LockStates.Reader;
if ((knownState & (LockStates.ReadersMask | LockStates.ReaderSignaled)) == LockStates.Reader)
{
isLastReader = true;
if ((knownState & LockStates.WaitingWritersMask) != 0)
{
writerEvent = TryGetOrCreateWriterEvent();
if (writerEvent == null)
{
// Similar to below, wait for some time and try again
Helpers.Sleep(100);
currentState = _state;
knownState = 0;
Debug.Assert(currentState != knownState);
continue;
}
modifyState += LockStates.WriterSignaled;
}
else if ((knownState & LockStates.WaitingReadersMask) != 0)
{
readerEvent = TryGetOrCreateReaderEvent();
if (readerEvent == null)
{
// Wait for some time and try again. Since a WaitingReaders bit is set, the event would usually
// already be created (if the waiting reader that called AcquireReaderLock is already waiting on the
// event, it would have created the event). However, AcquireReaderLock adds WaitingReader to the
// state before trying to create the event.
//
// This is such a situation, where the event has not yet been created, and likely due to the system
// being low on resources, this thread failed to create the event. We don't want to throw here,
// because it could potentially leave waiters waiting and cause a deadlock.
//
// Instead, we let the threads that set the WaitingReader bit throw, and here, just wait and try
// again. In a low-resource situation, eventually, all such new waiting readers would throw, and the
// WaitingReaders bits would not be set anymore, breaking the loop and releasing this thread.
Helpers.Sleep(100);
currentState = _state;
knownState = 0;
Debug.Assert(currentState != knownState);
continue;
}
modifyState += LockStates.ReaderSignaled;
}
else if (knownState == LockStates.Reader && (_readerEvent != null || _writerEvent != null))
{
cacheEvents = true;
modifyState += LockStates.CachingEvents;
}
}
Debug.Assert((knownState & LockStates.Writer) == 0);
Debug.Assert((knownState & LockStates.ReadersMask) != 0);
currentState = Interlocked.CompareExchange(ref _state, knownState + modifyState, knownState);
} while (currentState != knownState);
// Check for last reader
if (isLastReader)
{
// Check for waiting writers
if ((knownState & LockStates.WaitingWritersMask) != 0)
{
Debug.Assert((_state & LockStates.WriterSignaled) != 0);
Debug.Assert(writerEvent != null);
writerEvent.Set();
}
// Check for waiting readers
else if ((knownState & LockStates.WaitingReadersMask) != 0)
{
Debug.Assert((_state & LockStates.ReaderSignaled) != 0);
Debug.Assert(readerEvent != null);
readerEvent.Set();
}
// Check for the need to release events
else if (cacheEvents)
{
ReleaseEvents();
}
}
Debug.Assert(threadLocalLockEntry.IsFree);
}