465 lines
11 KiB
C++
465 lines
11 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// The include files for supporting classes.
|
|
//
|
|
// The include files for supporting classes consists of
|
|
// classes refered to or used in this class. The structure
|
|
// of each source module is as follows:
|
|
// 1. Include files.
|
|
// 2. Constants local to the class.
|
|
// 3. Data structures local to the class.
|
|
// 4. Data initializations.
|
|
// 5. Static functions.
|
|
// 6. Class functions.
|
|
// Sections that are not required are omitted.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "precomp.hxx"
|
|
|
|
#define DLL_IMPLEMENTATION
|
|
#define IMPLEMENTATION_EXPORT
|
|
#include <Sharelok.h>
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Class constructor.
|
|
//
|
|
// Create a new lock and initialize it. This call is not
|
|
// thread safe and should only be made in a single thread
|
|
// environment.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
CSharelock::CSharelock( SBIT32 lNewMaxSpins, SBIT32 lNewMaxUsers )
|
|
{
|
|
//
|
|
// Set the initial state.
|
|
//
|
|
m_lExclusive = 0;
|
|
m_lTotalUsers = 0;
|
|
m_lWaiting = 0;
|
|
|
|
//
|
|
// Check the configurable values.
|
|
//
|
|
if ( lNewMaxSpins > 0 )
|
|
{
|
|
m_lMaxSpins = lNewMaxSpins;
|
|
}
|
|
else
|
|
{
|
|
throw (TEXT("Maximum spins invalid in constructor for CSharelock"));
|
|
}
|
|
|
|
if ( (lNewMaxUsers > 0) && (lNewMaxUsers <= m_MaxShareLockUsers) )
|
|
{
|
|
m_lMaxUsers = lNewMaxUsers;
|
|
}
|
|
else
|
|
{
|
|
throw (TEXT("Maximum share invalid in constructor for CSharelock"));
|
|
}
|
|
|
|
//
|
|
// Create a semaphore to sleep on when the spin count exceeds
|
|
// its maximum.
|
|
//
|
|
if ( (m_hSemaphore = CreateSemaphore( NULL, 0, m_MaxShareLockUsers, NULL )) == NULL)
|
|
{
|
|
throw (TEXT("Create semaphore in constructor for CSharelock"));
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
|
|
//
|
|
// Set the initial state of any debug variables.
|
|
//
|
|
m_lTotalExclusiveLocks = 0;
|
|
m_lTotalShareLocks = 0;
|
|
m_lTotalSleeps = 0;
|
|
m_lTotalSpins = 0;
|
|
m_lTotalTimeouts = 0;
|
|
m_lTotalWaits = 0;
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Sleep waiting for the lock.
|
|
//
|
|
// We have decided it is time to sleep waiting for the lock
|
|
// to become free.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
BOOLEAN CSharelock::SleepWaitingForLock( SBIT32 lSleep )
|
|
{
|
|
//
|
|
// We have been spinning waiting for the lock but it
|
|
// has not become free. Hence, it is now time to
|
|
// give up and sleep for a while.
|
|
//
|
|
(void) InterlockedIncrement( (LPLONG) & m_lWaiting );
|
|
|
|
//
|
|
// Just before we go to sleep we do one final check
|
|
// to make sure that the lock is still busy and that
|
|
// there is someone to wake us up when it becomes free.
|
|
//
|
|
if ( m_lTotalUsers > 0 )
|
|
{
|
|
#ifdef _DEBUG
|
|
//
|
|
// Count the number of times we have slept on this lock.
|
|
//
|
|
(void) InterlockedIncrement( (LPLONG) & m_lTotalSleeps );
|
|
|
|
#endif
|
|
//
|
|
// When we sleep we awoken when the lock becomes free
|
|
// or when we timeout. If we timeout we simply exit
|
|
// after decrementing various counters.
|
|
if (WaitForSingleObject( m_hSemaphore, lSleep ) != WAIT_OBJECT_0 )
|
|
{
|
|
#ifdef _DEBUG
|
|
//
|
|
// Count the number of times we have timed out
|
|
// on this lock.
|
|
//
|
|
(void) InterlockedIncrement( (LPLONG) & m_lTotalTimeouts );
|
|
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Lucky - the lock was just freed so lets
|
|
// decrement the sleep count and exit without
|
|
// sleeping.
|
|
//
|
|
(void) InterlockedDecrement( (LPLONG) & m_lWaiting );
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update the spin limit.
|
|
//
|
|
// Update the maximum number of spins while waiting for the lock.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
BOOLEAN CSharelock::UpdateMaxSpins( SBIT32 lNewMaxSpins )
|
|
{
|
|
if ( lNewMaxSpins > 0 )
|
|
{
|
|
m_lMaxSpins = lNewMaxSpins;
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update the sharing limit.
|
|
//
|
|
// Update the maximum number of users that can share the lock.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
BOOLEAN CSharelock::UpdateMaxUsers( SBIT32 lNewMaxUsers )
|
|
{
|
|
if ( (lNewMaxUsers > 0) && (lNewMaxUsers <= m_MaxShareLockUsers) )
|
|
{
|
|
ClaimExclusiveLock();
|
|
|
|
m_lMaxUsers = lNewMaxUsers;
|
|
|
|
ReleaseExclusiveLock();
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Wait for an exclusive lock.
|
|
//
|
|
// Wait for the spinlock to become free and then claim it.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
BOOLEAN CSharelock::WaitForExclusiveLock( SBIT32 lSleep )
|
|
{
|
|
#ifdef _DEBUG
|
|
register SBIT32 lSpins = 0;
|
|
register SBIT32 lWaits = 0;
|
|
|
|
#endif
|
|
while ( m_lTotalUsers != 1 )
|
|
{
|
|
//
|
|
// The lock is busy so release it and spin waiting
|
|
// for it to become free.
|
|
//
|
|
(void) InterlockedDecrement( (LPLONG) & m_lTotalUsers );
|
|
|
|
//
|
|
// Find out if we are allowed to spin and sleep if
|
|
// necessary.
|
|
//
|
|
if ( (lSleep > 0) || (lSleep == INFINITE) )
|
|
{
|
|
register SBIT32 lCount;
|
|
|
|
//
|
|
// Wait by spinning and repeatedly testing the lock.
|
|
// We exit when the lock becomes free or the spin limit
|
|
// is exceeded.
|
|
//
|
|
for (lCount = (NumProcessors() < 2) ? 0 : m_lMaxSpins;
|
|
(lCount > 0) && (m_lTotalUsers > 0);
|
|
lCount -- )
|
|
;
|
|
#ifdef _DEBUG
|
|
|
|
lSpins += (m_lMaxSpins - lCount);
|
|
lWaits ++;
|
|
#endif
|
|
|
|
//
|
|
// We have exhusted our spin count so it is time to
|
|
// sleep waiting for the lock to clear.
|
|
//
|
|
if ( lCount == 0 )
|
|
{
|
|
//
|
|
// We have decide that we need to sleep but are
|
|
// still holding an exclusive lock so lets drop it
|
|
// before sleeping.
|
|
//
|
|
(void) InterlockedDecrement( (LPLONG) & m_lExclusive );
|
|
|
|
//
|
|
// We have decide to go to sleep. If the sleep time
|
|
// is not 'INFINITE' then we must subtract the time
|
|
// we sleep from our maximum sleep time. If the
|
|
// sleep time is 'INFINITE' then we can just skip
|
|
// this step.
|
|
//
|
|
if ( lSleep != INFINITE )
|
|
{
|
|
register DWORD dwStartTime = GetTickCount();
|
|
|
|
if ( ! SleepWaitingForLock( lSleep ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
lSleep -= ((GetTickCount() - dwStartTime) + 1);
|
|
lSleep = (lSleep > 0) ? lSleep : 0;
|
|
}
|
|
else
|
|
{
|
|
if ( ! SleepWaitingForLock( lSleep ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We have woken up again so lets reclaim the
|
|
// exclusive lock we had earlier.
|
|
//
|
|
(void) InterlockedIncrement( (LPLONG) & m_lExclusive );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We have decide that we need to exit but are still
|
|
// holding an exclusive lock. so lets drop it and leave.
|
|
//
|
|
(void) InterlockedDecrement( (LPLONG) & m_lExclusive );
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Lets test the lock again.
|
|
//
|
|
InterlockedIncrement( (LPLONG) & m_lTotalUsers );
|
|
}
|
|
#ifdef _DEBUG
|
|
|
|
(void) InterlockedExchangeAdd( (LPLONG) & m_lTotalSpins, (LONG) lSpins );
|
|
(void) InterlockedExchangeAdd( (LPLONG) & m_lTotalWaits, (LONG) lWaits );
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Wait for a shared lock.
|
|
//
|
|
// Wait for the lock to become free and then claim it.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
BOOLEAN CSharelock::WaitForShareLock( SBIT32 lSleep )
|
|
{
|
|
#ifdef _DEBUG
|
|
register SBIT32 lSpins = 0;
|
|
register SBIT32 lWaits = 0;
|
|
|
|
#endif
|
|
while ( (m_lExclusive > 0) || (m_lTotalUsers > m_lMaxUsers) )
|
|
{
|
|
//
|
|
// The lock is busy so release it and spin waiting
|
|
// for it to become free.
|
|
//
|
|
(void) InterlockedDecrement( (LPLONG) & m_lTotalUsers );
|
|
|
|
if ( (lSleep > 0) || (lSleep == INFINITE) )
|
|
{
|
|
register SBIT32 lCount;
|
|
|
|
//
|
|
// Wait by spinning and repeatedly testing the lock.
|
|
// We exit when the lock becomes free or the spin limit
|
|
// is exceeded.
|
|
//
|
|
for (lCount = (NumProcessors() < 2) ? 0 : m_lMaxSpins;
|
|
(lCount > 0) && ((m_lExclusive > 0) || (m_lTotalUsers >= m_lMaxUsers));
|
|
lCount -- )
|
|
;
|
|
#ifdef _DEBUG
|
|
|
|
lSpins += (m_lMaxSpins - lCount);
|
|
lWaits ++;
|
|
#endif
|
|
|
|
//
|
|
// We have exhusted our spin count so it is time to
|
|
// sleep waiting for the lock to clear.
|
|
//
|
|
if ( lCount == 0 )
|
|
{
|
|
//
|
|
// We have decide to go to sleep. If the sleep time
|
|
// is not 'INFINITE' then we must subtract the time
|
|
// we sleep from our maximum sleep time. If the
|
|
// sleep time is 'INFINITE' then we can just skip
|
|
// this step.
|
|
//
|
|
if ( lSleep != INFINITE )
|
|
{
|
|
register DWORD dwStartTime = GetTickCount();
|
|
|
|
if ( ! SleepWaitingForLock( lSleep ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
lSleep -= ((GetTickCount() - dwStartTime) + 1);
|
|
lSleep = (lSleep > 0) ? lSleep : 0;
|
|
}
|
|
else
|
|
{
|
|
if ( ! SleepWaitingForLock( lSleep ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Lets test the lock again.
|
|
//
|
|
(void) InterlockedIncrement( (LPLONG) & m_lTotalUsers );
|
|
}
|
|
#ifdef _DEBUG
|
|
|
|
|
|
(void) InterlockedExchangeAdd( (LPLONG) & m_lTotalSpins, (LONG) lSpins );
|
|
(void) InterlockedExchangeAdd( (LPLONG) & m_lTotalWaits, (LONG) lWaits );
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Wake all sleepers.
|
|
//
|
|
// Wake all the sleepers who are waiting for the spinlock.
|
|
// All sleepers are woken because this is much more efficent
|
|
// and it is known that the lock latency is short.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void CSharelock::WakeAllSleepers( void )
|
|
{
|
|
register LONG lWakeup = InterlockedExchange( (LPLONG) & m_lWaiting, 0 );
|
|
|
|
if ( lWakeup > 0 )
|
|
{
|
|
//
|
|
// Wake up all sleepers as the lock has just been freed.
|
|
// It is a straight race to decide who gets the lock next.
|
|
//
|
|
if ( ! ReleaseSemaphore( m_hSemaphore, lWakeup, NULL ) )
|
|
{
|
|
throw (TEXT("Wakeup failed in ReleaseLock()"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// When multiple threads pass through the critical section rapidly
|
|
// it is possible for the 'Waiting' count to become negative.
|
|
// This should be very rare but such a negative value needs to be
|
|
// preserved.
|
|
//
|
|
InterlockedExchangeAdd( (LPLONG) & m_lWaiting, lWakeup );
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Class destructor.
|
|
//
|
|
// Destroy a lock. This call is not thread safe and should
|
|
// only be made in a single thread environment.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
CSharelock::~CSharelock( void )
|
|
{
|
|
if ( ! CloseHandle( m_hSemaphore ) )
|
|
{
|
|
throw (TEXT("Close semaphore in destructor for CSharelock"));
|
|
}
|
|
}
|