public void DowngradeFromWriterLock(ref LockCookie lockCookie)
{
int threadID = GetCurrentThreadID();
if (_writerID != threadID)
{
throw GetNotOwnerException();
}
// Validate cookie
LockCookieFlags flags = lockCookie._flags;
ushort requestedWriterLevel = lockCookie._writerLevel;
if ((flags & LockCookieFlags.Invalid) != 0 ||
lockCookie._threadID != threadID ||
(
// Cannot downgrade to a writer level that is greater than or equal to the current
(flags & (LockCookieFlags.OwnedWriter | LockCookieFlags.OwnedNone)) != 0 &&
_writerLevel <= requestedWriterLevel
))
{
throw GetInvalidLockCookieException();
}
// Check if the thread was a reader
if ((flags & LockCookieFlags.OwnedReader) != 0)
{
Debug.Assert(_writerLevel > 0);
ThreadLocalLockEntry threadLocalLockEntry = ThreadLocalLockEntry.GetOrCreateCurrent(_lockID);
// Downgrade to a reader
_writerID = InvalidThreadID;
_writerLevel = 0;
ManualResetEventSlim readerEvent = null;
int currentState = _state;
int knownState;
do
{
knownState = currentState;
int modifyState = LockStates.Reader - LockStates.Writer;
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;
}
Debug.Assert((knownState & LockStates.ReadersMask) == 0);
currentState = Interlocked.CompareExchange(ref _state, knownState + modifyState, knownState);
} while (currentState != knownState);
// Check for waiting readers
if ((knownState & LockStates.WaitingReadersMask) != 0)
{
Debug.Assert((_state & LockStates.ReaderSignaled) != 0);
Debug.Assert(readerEvent != null);
readerEvent.Set();
}
// Restore reader nesting level
threadLocalLockEntry._readerLevel = lockCookie._readerLevel;
}
else if ((flags & (LockCookieFlags.OwnedWriter | LockCookieFlags.OwnedNone)) != 0)
{
// Original code:
// ReleaseWriterLock();
// Debug.Assert((flags & LockCookieFlags.OwnedWriter) != 0 || _writerID != threadID);
//
// Previously, the lock cookie was ignored on this path. UpgradeToWriterLock allows upgrading from an unlocked
// state or when the write lock is already held, where it just calls AcquireWriteLock. To compensate, I
// DowngradeFromWriterLock intends to just behave as ReleaseWriterLock.
//
// However, the lock cookie could be several operations old. Consider:
// lockCookie = UpgradeToWriterLock()
// AcquireWriterLock()
// DowngradeFromWriterLock(ref lockCookie)
//
// Since the lock cookie indicates that no lock was held at the time of the upgrade, The ReleaseWriterLock in
// the original code above does not result in releasing all writer locks as requested by the lock cookie and as
// expected by the assertion. The code should respect the lock cookie (as it does in the case above where the
// lock cookie indicates that a read lock was held), and restore the writer level appropriately.
//
// Similarly, when the lock cookie does indicate that a write lock was held, the downgrade does not restore the
// write lock recursion level to that indicated by the lock cookie. Consider:
// AcquireWriterLock()
// lockCookie = UpgradeToWriterLock()
// AcquireWriterLock()
// DowngradeFromWriterLock(ref lockCookie) // does not restore recursion level of write lock!
Debug.Assert(_writerLevel > 0);
Debug.Assert(_writerLevel > requestedWriterLevel);
if (requestedWriterLevel > 0)
{
_writerLevel = requestedWriterLevel;
}
else
{
if (_writerLevel != 1)
{
_writerLevel = 1;
}
ReleaseWriterLock();
}
Debug.Assert((flags & LockCookieFlags.OwnedWriter) != 0 || _writerID != threadID);
}
// Update the validation fields of the cookie
lockCookie._flags = LockCookieFlags.Invalid;
}