Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
Fix new deadlock possibility in ReaderWriterLockSlim from a recent ch…
Browse files Browse the repository at this point in the history
…ange

Fixed the following scenario that would lead to deadlock (rare, but frequently reproducible if code is changed):
- Thread T0 signals the write waiter event or the upgradeable read waiter event to wake a waiter
- There are no threads waiting on the event, but T1 is in WaitOnEvent() after exiting the spin lock and before actually waiting on the event (that is, it's recorded that there is one waiter for the event). It remains in this region for a while, in the repro case it typically gets context-switched out.
- T2 acquires the RW lock in some fashion that blocks T0 from acquiring the RW lock
- T0 fails to acquire the RW lock enough times for it to enter WaitOnEvent for the same event as T1
- T0 resets the event
- T2 releases the RW lock and does not wake a waiter because the reset at the previous step lost a signal but _waiterStates was not updated to reflect that
- T2 exits (or otherwise no longer attempts to acquire the RW lock), and there are no other threads spinning for the RW lock
- T1 and other threads begin waiting on the event, but there's no longer any thread that would wake them
  • Loading branch information
kouvel committed Oct 5, 2017
1 parent 6573823 commit 1d1c77e
Showing 1 changed file with 32 additions and 22 deletions.
54 changes: 32 additions & 22 deletions src/mscorlib/shared/System/Threading/ReaderWriterLockSlim.cs
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,39 @@ private bool WaitOnEvent(
Debug.Assert(_spinLock.IsHeld);
#endif

WaiterStates waiterSignaledState = WaiterStates.None;
EnterSpinLockReason enterMyLockReason;
switch (enterLockType)
{
case EnterLockType.UpgradeableRead:
waiterSignaledState = WaiterStates.UpgradeableReadWaiterSignaled;
goto case EnterLockType.Read;

case EnterLockType.Read:
enterMyLockReason = EnterSpinLockReason.EnterAnyRead;
break;

case EnterLockType.Write:
waiterSignaledState = WaiterStates.WriteWaiterSignaled;
enterMyLockReason = EnterSpinLockReason.EnterWrite;
break;

default:
Debug.Assert(enterLockType == EnterLockType.UpgradeToWrite);
enterMyLockReason = EnterSpinLockReason.UpgradeToWrite;
break;
}

// It was not possible to acquire the RW lock because some other thread was holding some type of lock. The other
// thread, when it releases its lock, will wake appropriate waiters. Along with resetting the wait event, clear the
// waiter signaled bit for this type of waiter if applicable, to indicate that a waiter of this type is no longer
// signaled.
if (waiterSignaledState != WaiterStates.None && (_waiterStates & waiterSignaledState) != WaiterStates.None)
{
_waiterStates &= ~waiterSignaledState;
}
waitEvent.Reset();

numWaiters++;
HasNoWaiters = false;

Expand All @@ -986,28 +1018,6 @@ private bool WaitOnEvent(
}
finally
{
WaiterStates waiterSignaledState = WaiterStates.None;
EnterSpinLockReason enterMyLockReason;
switch (enterLockType)
{
case EnterLockType.UpgradeableRead:
waiterSignaledState = WaiterStates.UpgradeableReadWaiterSignaled;
goto case EnterLockType.Read;

case EnterLockType.Read:
enterMyLockReason = EnterSpinLockReason.EnterAnyRead;
break;

case EnterLockType.Write:
waiterSignaledState = WaiterStates.WriteWaiterSignaled;
enterMyLockReason = EnterSpinLockReason.EnterWrite;
break;

default:
Debug.Assert(enterLockType == EnterLockType.UpgradeToWrite);
enterMyLockReason = EnterSpinLockReason.UpgradeToWrite;
break;
}
_spinLock.Enter(enterMyLockReason);

--numWaiters;
Expand Down

0 comments on commit 1d1c77e

Please sign in to comment.