//	++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//	SYNCHRO.CPP
//
//	Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
//
#include "_synchro.h"
#include <ex\synchro.h>

//	========================================================================
//
//	CLASS CMRWLock
//
//	EnterRead()/LeaveRead() respectively lets a reader enter or leave
//	the lock.  If there is a writer or promotable reader in the lock,
//	entry is delayed until the writer/promotable reader leaves.
//
//	EnterWrite()/LeaveWrite() respectively lets a single writer enter
//	or leave the lock.  If there are any readers in the lock, entry
//	is delayed until they leave.  If there is another writer or a
//	promotable reader in the lock, entry is delayed until it leaves.
//
//	EnterPromote()/LeavePromote() respectively lets a single promotable
//	reader enter or leave the lock.  If there is a writer or another
//	promotable reader in the lock, entry is delayed until the
//	writer/promotable reader leaves.  Otherwise entry is immediate,
//	even if there are other (non-promotable) readers in the lock.
//	Promote() promotes the promotable reader to a writer.  If there
//	are readers in the lock, promotion is delayed until they leave.
//
//	Once a writer or promotable reader has entered the lock, it may
//	reenter the lock as a reader, writer or promotable reader without
//	delay.  A reader cannot reenter the lock as a writer or promotable.
//

//	------------------------------------------------------------------------
//
//	CMRWLock::CMRWLock()
//
CMRWLock::CMRWLock() :
   m_lcReaders(0),
   m_dwWriteLockOwner(0),
   m_dwPromoterRecursion(0)
{
}

//	------------------------------------------------------------------------
//
//	CMRWLock::FInitialize()
//
BOOL
CMRWLock::FInitialize()
{
	return m_evtEnableReaders.FCreate( NULL,	// default security
									   TRUE,	// manual-reset
									   FALSE,	// initially non-signalled
									   NULL )   // unnamed

		&& m_evtEnableWriter.FCreate(  NULL,	// default security
									   FALSE,	// auto-reset
									   FALSE,	// initially non-signalled
									   NULL );  // unnamed
}

//	------------------------------------------------------------------------
//
//	CMRWLock::EnterRead()
//
void
CMRWLock::EnterRead()
{
	(void) FAcquireReadLock(TRUE); // fBlock
}

//	------------------------------------------------------------------------
//
//	CMRWLock::FTryEnterRead()
//
BOOL
CMRWLock::FTryEnterRead()
{
	return FAcquireReadLock(FALSE); // fBlock
}

//	------------------------------------------------------------------------
//
//	CMRWLock::FAcquireReadLock()
//
BOOL
CMRWLock::FAcquireReadLock(BOOL fAllowCallToBlock)
{
	//
	//	Loop around trying to enter the lock until successful
	//
	for ( ;; )
	{
		//
		//	Poll the reader count/write lock
		//
		LONG lcReaders = m_lcReaders;

		//
		//	If the write lock is held ...
		//
		if ( lcReaders & WRITE_LOCKED )
		{
			//
			//	... check whether the writer is on this thread.
			//	If it is, then let this thread reenter the
			//	lock as a reader.  Do not update the reader
			//	count in this case.
			//
			if ( m_dwWriteLockOwner == GetCurrentThreadId() )
				break;

			//
			//	If the writer is not on this thread, then wait
			//	until the writer leaves, then re-poll the
			//	reader count/write lock and try again.
			//
			//	We only block if the caller allows us to block.  If this is
			//	a FTryEnterRead call, we return FALSE right away.
			//
			if ( fAllowCallToBlock )
			{
				m_evtEnableReaders.Wait();
			}
			else
			{
				return FALSE;
			}
		}

		//
		//	Otherwise, the write lock was not held, so
		//	try to enter the lock as a reader.  This only
		//	succeeds when no readers or writers enter or leave
		//	the lock between the time the reader count/
		//	write lock is polled above and now.  If what is in
		//	the lock has changed, the whole operation is retried
		//	until the lock state doesn't change.
		//
		else
		{
			if ( lcReaders == /*reinterpret_cast<LONG>*/(
								InterlockedCompareExchange(
									(&m_lcReaders),
									(lcReaders + 1),
									(lcReaders)))  )
#ifdef NEVER
									reinterpret_cast<PVOID *>(&m_lcReaders),
									reinterpret_cast<PVOID>(lcReaders + 1),
									reinterpret_cast<PVOID>(lcReaders)))  )
#endif // NEVER
			{
				break;
			}
		}
	}

	//	If we made it to this point, we have acquired the read lock.
	//
	return TRUE;
}

//	------------------------------------------------------------------------
//
//	CMRWLock::LeaveRead()
//
void
CMRWLock::LeaveRead()
{
	//
	//	If the thread on which the reader is leaving also owns
	//	the write lock, then the reader leaving has no effect,
	//	as did entering.
	//
	if ( m_dwWriteLockOwner == GetCurrentThreadId() )
		return;

	//
	//	Otherwise, atomically decrement the reader count and
	//	check if a writer is waiting to enter the lock.
	//	If the reader count goes to 0 and a writer is waiting
	//	to enter the lock, then notify the writer that
	//	it is safe to enter.
	//
	if ( WRITE_LOCKED == InterlockedDecrement(&m_lcReaders) )
		m_evtEnableWriter.Set();
}

//	------------------------------------------------------------------------
//
//	CMRWLock::EnterWrite()
//
void
CMRWLock::EnterWrite()
{
	//
	//	A writer is just a promotable reader that promotes immediately
	//
	EnterPromote();
	Promote();
}

//	------------------------------------------------------------------------
//
//	CMRWLock::FTryEnterWrite()
//
BOOL
CMRWLock::FTryEnterWrite()
{
	BOOL fSuccess;

	//
	//	Try to enter the lock as a promotable reader.
	//	Promote to a writer immediately if successful
	//	and return the status of the operation.
	//
	fSuccess = FTryEnterPromote();

	if ( fSuccess )
		Promote();

	return fSuccess;
}

//	------------------------------------------------------------------------
//
//	CMRWLock::LeaveWrite()
//
void
CMRWLock::LeaveWrite()
{
	LeavePromote();
}

//	------------------------------------------------------------------------
//
//	CMRWLock::EnterPromote()
//
void
CMRWLock::EnterPromote()
{
	//
	//	Grab the writer critical section to ensure that no other thread
	//	is already in the lock as a writer or promotable reader.
	//
	m_csWriter.Enter();

	//
	//	Bump the promoter recursion count
	//
	++m_dwPromoterRecursion;
}

//	------------------------------------------------------------------------
//
//	CMRWLock::FTryEnterPromote()
//
BOOL
CMRWLock::FTryEnterPromote()
{
	BOOL fSuccess;

	//
	//	Try to enter the writer critical section.
	//	Bump the recursion count if successful and
	//	return the status of the operation.
	//
	fSuccess = m_csWriter.FTryEnter();

	if ( fSuccess )
		++m_dwPromoterRecursion;

	return fSuccess;
}

//	------------------------------------------------------------------------
//
//	CMRWLock::Promote()
//
void
CMRWLock::Promote()
{
	//
	//	If the promotable reader has already been promoted
	//	then don't promote it again
	//
	if ( GetCurrentThreadId() == m_dwWriteLockOwner )
		return;

	//
	//	Assert that no other writer owns the lock.
	//
	Assert( 0 == m_dwWriteLockOwner );
	Assert( !(m_lcReaders & WRITE_LOCKED) );

	//
	//	Claim the lock
	//
	m_dwWriteLockOwner = GetCurrentThreadId();

	//
	//	Stop readers from entering the lock
	//
	m_evtEnableReaders.Reset();

	//
	//	If there are any readers in the lock
	//	then wait for them to leave.  The InterlockedExchangeOr()
	//	is used to ensure that the test is atomic.
	//
	if ( InterlockedExchangeOr( &m_lcReaders, WRITE_LOCKED ) )
		m_evtEnableWriter.Wait();

	//
	//	Assert that the (promoted) writer is now the only thing in the lock
	//
	Assert( WRITE_LOCKED == m_lcReaders );
}

//	------------------------------------------------------------------------
//
//	CMRWLock::LeavePromote()
//
void
CMRWLock::LeavePromote()
{
	//
	//	No one should attempt to leave a promote block
	//	that they never entered.
	//
	Assert( m_dwPromoterRecursion > 0 );

	//
	//	If the promotable reader promoted to a writer
	//	then start allowing readers back into the lock
	//	once the promoter recursion count reaches 0
	//
	if ( --m_dwPromoterRecursion == 0 &&
		 GetCurrentThreadId() == m_dwWriteLockOwner )
	{
		//
		//	Clear the write flag to allow new readers
		//	to start entering the lock.
		//
		m_lcReaders = 0;

		//
		//	Unblock any threads with readers that are
		//	already waiting to enter the lock.
		//
		m_evtEnableReaders.Set();

		//
		//	Release ownership of the write lock
		//
		m_dwWriteLockOwner = 0;
	}

	//
	//	Release the writer's/promoter's critical section reference.
	//	When this the last such reference is released, a new
	//	promoter/writer may enter the lock.
	//
	m_csWriter.Leave();
}

//	========================================================================
//
//	FREE FUNCTIONS
//

//	------------------------------------------------------------------------
//
//	InterlockedExchangeOr()
//
//	This function performs an atomic logical OR of a value to a variable.
//	The function prevents more than one thread from using the same
//	variable simultaneously.  (Well, actually, it spins until it
//	gets a consistent result, but who's counting...)
//
//	Returns the value of the variable before the logical OR was performed
//
LONG InterlockedExchangeOr( LONG * plVariable, LONG lOrBits )
{
	//	The rather cryptic way this works is:
	//
	//	Get the instantaneous value of the variable.  Stuff it into a
	//	local variable so that it cannot be changed by another thread.
	//	Then try to replace the variable with this value OR'd together
	//	with the OR bits.  But only replace if the variable's value
	//	is still the same as the local variable.  If it isn't, then
	//	another thread must have changed the value between the two
	//	operations, so keep trying until they both succeed as if
	//	they had executed as one.  Once the operation succeeds in
	//	changing the value atomically, return  the previous value.
	//
	for ( ;; )
	{
		LONG lValue = *plVariable;

		if ( lValue == /*reinterpret_cast<LONG>*/(
							InterlockedCompareExchange(
								(plVariable),
								(lValue | lOrBits),
								(lValue))) )
#ifdef NEVER
								reinterpret_cast<PVOID *>(plVariable),
								reinterpret_cast<PVOID>(lValue | lOrBits),
								reinterpret_cast<PVOID>(lValue))) )
#endif // NEVER
			return lValue;
	}
}