I stumbled upon a weird FRWLock behavior today.
This code snippet recreates the scenario and ends up deadlock in the following state:
WriteThreadState::WaitingForLock
ReadThreadState::ReadLockedTrice_Reentry_AfterWriteLocked
Note that it passes the ReadLockedTwice_Reentry without an issue. Reentering a read on the same thread is not a problem. The problem happens as soon as there is a write lock ‘pending’.
I suppose the expectations are that the write lock has precedence over a new read lock. This behavior is, to my knowledge, undocumented. Also, since the reentry of a read is permitted, it makes this very error prone.
This code was only written to prove a point.
FRWLock Lock;
std::atomic_bool bIsReadLocked = false;
std::atomic<int64> WriteLockEntryCycleTime = -1;
enum class WriteThreadState
{
Prelock,
WaitingForLock,
WaitingForReadUnlocked,
Postlocks
} WriteState = WriteThreadState::Prelock;
enum class ReadThreadState
{
Prelock,
ReadLocked,
ReadLockedTwice_Reentry,
ReadLockedTrice_Reentry_AfterWriteLocked,
Postlocks
} ReadState = ReadThreadState::Prelock;
ParallelFor(2, [&](int32 TestIndex) {
if (TestIndex == 0)
{
{
while (!bIsReadLocked)
;
WriteState = WriteThreadState::WaitingForLock;
WriteLockEntryCycleTime = FPlatformTime::Cycles();
FWriteScopeLock WriteLock(Lock);
WriteState = WriteThreadState::WaitingForReadUnlocked;
while (bIsReadLocked)
;
}
WriteState = WriteThreadState::Postlocks;
WriteLockEntryCycleTime = -WriteLockEntryCycleTime;
}
else if (TestIndex == 1)
{
{
FReadScopeLock FirstReadLock(Lock);
ReadState = ReadThreadState::ReadLocked;
FReadScopeLock SecondReadLock(Lock); // Reentry passes.
ReadState = ReadThreadState::ReadLockedTwice_Reentry;
bIsReadLocked = true;
while (WriteLockEntryCycleTime < 0)
;
// Wait for the write lock to enter.
while (FPlatformTime::Cycles() < WriteLockEntryCycleTime + 10000)
;
// ----------------------- Deadlocks here -------------------------------------
ReadState = ReadThreadState::ReadLockedTrice_Reentry_AfterWriteLocked;
FReadScopeLock ThirdReadLock(Lock);
}
bIsReadLocked = false;
ReadState = ReadThreadState::Postlocks;
}
});