//
//  Copyright (c) 2001, Reson, Inc. All Rights Reserved.
//
//  Filename:   SWMRG.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus - Modified and adapted from Jeffery Richter's "Advanced Windows"
//                         non object oriented version.
//
//  Purpose:    Single-writer, multiple-reader guard synchronization class.
//
//  Notes:      1)  The lpszName to the constructor names this object. Specify NULL if
//                  if you don't want to share the object across process boundaries.
//
//              2)  Only destroy this object when when no writer or reader threads in the calling
//                  are not waiting.

#include "StdAfx.h"
#include "Swmrg.h"

#ifndef DimOf_m
#define DimOf_m( a )    ( sizeof( (a) ) / sizeof( ((a)[ 0 ]) ) )
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// CSWMRG class implementation.

CSWMRG::CSWMRG( LPCTSTR lpszName )
{
    ManageDllState_m();

    bool    bOk;
    TCHAR   szFullObjName[ 100 ];
    LPCTSTR lpszObjName;

    m_hMutexNoWriter    = NULL;
    m_hEventNoReaders   = NULL;
    m_hSemNumReaders    = NULL;

    // This mutex guards access to the other objects
    // managed by this data structure and also indicates 
    // whether there are any writer threads writing.
    // Initially no thread owns the mutex.

    lpszObjName = ConstructObjName( __TEXT("MUTEXNOWRITER"),
                                    lpszName,
                                    szFullObjName,
                                    DimOf_m( szFullObjName ),
                                    &bOk );
    if ( bOk )
    {
        m_hMutexNoWriter = ::CreateMutex( NULL, FALSE, lpszObjName );
    }

    // Create a manual-reset event that is signalled when no reader threads are reading.
    // Initially no reader threads are reading.

    lpszObjName = ConstructObjName( __TEXT( "EVENTNOREADERS" ),
                                    lpszName,
                                    szFullObjName,
                                    DimOf_m( szFullObjName ),
                                    &bOk );
    if ( bOk )
    {
        m_hEventNoReaders = ::CreateEvent( NULL, TRUE, TRUE, lpszObjName );
    }

    // Number of reader threads that are reading... initially no reader threads are reading.

    lpszObjName = ConstructObjName( __TEXT( "NUMREADERS" ), lpszName, szFullObjName, DimOf_m( szFullObjName ), &bOk );

    if ( bOk )
    {
        m_hSemNumReaders = ::CreateSemaphore( NULL, 0, 0x7fffffff, lpszObjName );
    }

    if ( ( m_hMutexNoWriter  == NULL ) || 
         ( m_hEventNoReaders == NULL ) || 
         ( m_hSemNumReaders  == NULL )  )
    {
        // If a synchronization object could not be created,
        // destroy any created objects and return failure.

        delete this;
        bOk = false;
    }
    else
    {
        bOk = true;
    }

    m_bInitialized = bOk;
}

bool CSWMRG::IsInitialized( void ) const
{
    ManageDllState_m();

    return m_bInitialized;
}

CSWMRG::~CSWMRG ( void )
{
    ManageDllState_m();

    if ( m_hMutexNoWriter != NULL )
    {
        CloseHandle( m_hMutexNoWriter );
        m_hMutexNoWriter = NULL;
    }

    if ( m_hEventNoReaders != NULL )
    {
        CloseHandle( m_hEventNoReaders );
        m_hEventNoReaders = NULL;
    }

    if ( m_hSemNumReaders != NULL )
    {
        CloseHandle( m_hSemNumReaders );
        m_hSemNumReaders = NULL;
    }

    m_bInitialized = false;
}

DWORD CSWMRG::WaitToWrite ( DWORD dwTimeout )
{
    ManageDllState_m();

    DWORD   dwResult;
    HANDLE  ahHandles[ 2 ];

    // We can write if: 1. The mutex guard is available and no other threads are writing.
    //                  2. No threads are reading.

    ahHandles[ 0 ] = m_hMutexNoWriter;
    ahHandles[ 1 ] = m_hEventNoReaders;

    if ( ( dwResult = WaitForMultipleObjects( 2, ahHandles, TRUE, dwTimeout ) ) != WAIT_TIMEOUT )
    {
        // This thread can write to the shared data.

        // Because a writer thread is writing, the mutex 
        // should not be released. This stops other 
        // writers and readers.
    }

    return dwResult;
}

void CSWMRG::DoneWriting( void )
{
    ManageDllState_m();

    ReleaseMutex( m_hMutexNoWriter );
}

DWORD CSWMRG::WaitToRead( DWORD dwTimeout )
{
    ManageDllState_m();

    DWORD   dwResult;
    LONG    lPreviousCount;

    // We can read if the mutex guard is available and no threads are writing.

    dwResult = WaitForSingleObject( m_hMutexNoWriter, dwTimeout );

    if ( dwResult != WAIT_TIMEOUT )
    {
        // This thread can read from the shared data. So, increment the number of readers
        // and if this is the first reader thread, set our event to reflect this.

        ReleaseSemaphore( m_hSemNumReaders, 1, &lPreviousCount );

        if ( lPreviousCount == 0 )
        {
            ResetEvent( m_hEventNoReaders );
        }

        // Allow other writer/reader threads to use object.

        ReleaseMutex( m_hMutexNoWriter );
    }

    return dwResult;
}

void CSWMRG::DoneReading( void )
{
    ManageDllState_m();

    HANDLE  ahHandles[2];
    LONG    lNumReaders;

    // We can stop reading if the mutex guard is available,
    // but when we stop reading we must also decrement the
    // number of reader threads.

    ahHandles[ 0 ] = m_hMutexNoWriter;
    ahHandles[ 1 ] = m_hSemNumReaders;

    WaitForMultipleObjects( 2, ahHandles, TRUE, INFINITE );

    // Get the remaining number of readers by releasing the
    // semaphore and then restoring the count by immediately
    // performing a wait.

    ReleaseSemaphore( m_hSemNumReaders, 1, &lNumReaders );
    WaitForSingleObject( m_hSemNumReaders, INFINITE );

    // If there are no remaining readers, set the event to relect this.

    if ( lNumReaders == 0 )
    {
        // If there are no reader threads, set our event to reflect this.

        SetEvent( m_hEventNoReaders );
    }

    // Allow other writer/reader threads to use the Swmrg synchronization object.

    ReleaseMutex( m_hMutexNoWriter );
}

//////////////////////////////////////////////////////////////////////////////
// Private member's.

LPCTSTR CSWMRG::ConstructObjName(   LPCTSTR     lpszPrefix,
                                    LPCTSTR     lpszSuffix,
                                    LPTSTR      lpszFullName,
                                    size_t      cbFullName,
                                    bool       *pbOk )      const
{
    *pbOk = true;

    if ( lpszSuffix == NULL )
    {
        return NULL;
    }

    // Check that the strings don't overflow the buffer.

    if ( ( _tcslen( lpszPrefix ) + _tcslen( lpszSuffix ) ) >= cbFullName )
    {
        *pbOk = false;
        return NULL;
    }

    _tcscpy( lpszFullName, lpszPrefix );
    _tcscat( lpszFullName, lpszSuffix );

    return lpszFullName;
}

