FRWLock unexpected deadlock on read reentry

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;
	}
});

FRWLock is not RECURSIVE

Thanks for the info!

Documented here in case you didn’t know like me