2025-04-27 07:49:33 -04:00

6265 lines
156 KiB
C++

/*==========================================================================
*
* Copyright (C) 1998-2000 Microsoft Corporation. All Rights Reserved.
*
* File: ThreadPool.cpp
* Content: main job thread pool
*
*
* History:
* Date By Reason
* ==== == ======
* 11/25/98 jtk Created
***************************************************************************/
#include "dnwsocki.h"
#undef DPF_SUBCOMP
#define DPF_SUBCOMP DN_SUBCOMP_WSOCK
//**********************************************************************
// Constant definitions
//**********************************************************************
//
// types of threads
//
typedef enum _THREAD_TYPE
{
THREAD_TYPE_UNKNOWN, // unknown
THREAD_TYPE_PRIMARY_WIN9X, // primary Win9x thread
THREAD_TYPE_SECONDARY_WIN9X, // secondary Win9x thread
} THREAD_TYPE;
//
// events for threads
//
enum
{
EVENT_INDEX_STOP_ALL_THREADS = 0,
EVENT_INDEX_PENDING_JOB = 1,
EVENT_INDEX_WAKE_NT_TIMER_THREAD = 1,
EVENT_INDEX_WINSOCK_2_SEND_COMPLETE = 2,
EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE = 3,
EVENT_INDEX_NATHELP_UPDATE = 4,
EVENT_INDEX_MAX
};
//
// times to wait in milliseconds when polling for work thread shutdown
//
#define WORK_THREAD_CLOSE_SLEEP_TIME 100
//
// select polling period for writes (milliseconds)
//
static const DWORD g_dwSelectTimeSlice = 2;
//**********************************************************************
// Macro definitions
//**********************************************************************
//**********************************************************************
// Structure definitions
//**********************************************************************
typedef struct _TIMER_OPERATION_ENTRY
{
CBilink Linkage; // list links
void *pContext; // user context passed back in timer events
//
// timer information
//
BOOL fPerformImmediately; // whether the operation should occur right away, or whether it should be delayed until the timeout elapses
UINT_PTR uRetryCount; // number of times to retry this event
BOOL fRetryForever; // Boolean for retrying forever
DN_TIME RetryInterval; // time between enums (milliseconds)
DN_TIME IdleTimeout; // time at which the command sits idle after all retrys are complete
BOOL fIdleWaitForever; // Boolean for waiting forever in idle state
DN_TIME NextRetryTime; // time at which this event will fire next (milliseconds)
TIMER_EVENT_CALLBACK *pTimerCallback; // callback for when this event fires
TIMER_EVENT_COMPLETE *pTimerComplete; // callback for when this event is complete
#undef DPF_MODNAME
#define DPF_MODNAME "_TIMER_OPERATION_ENTRY::TimerOperationFromLinkage"
static TIMER_OPERATION_ENTRY *TimerOperationFromLinkage( CBilink *const pLinkage )
{
DNASSERT( pLinkage != NULL );
DBG_CASSERT( OFFSETOF( _TIMER_OPERATION_ENTRY, Linkage ) == 0 );
return reinterpret_cast<_TIMER_OPERATION_ENTRY*>( pLinkage );
}
} TIMER_OPERATION_ENTRY;
//
// structure for common data in Win9x thread
//
typedef struct _WIN9X_CORE_DATA
{
DN_TIME NextTimerJobTime; // time when the next timer job needs service
HANDLE hWaitHandles[ EVENT_INDEX_MAX ]; // handles for waiting on
DWORD dwTimeToNextJob; // time to next job
BOOL fTimerJobsActive; // Boolean indicating that there are active jobs
BOOL fLooping; // Boolean indicating that this thread is still running
} WIN9X_CORE_DATA;
//
// information passed to the Win9x workhorse thread
//
typedef struct _WIN9X_THREAD_DATA
{
CThreadPool *pThisThreadPool; // pointer to this object
} WIN9X_THREAD_DATA;
//
// information passed to the IOCompletion thread
//
typedef struct _IOCOMPLETION_THREAD_DATA
{
CThreadPool *pThisThreadPool; // pointer to this object
} IOCOMPLETION_THREAD_DATA;
//
// structure passed to dialog threads
//
typedef struct _DIALOG_THREAD_PARAM
{
DIALOG_FUNCTION *pDialogFunction;
void *pContext;
CThreadPool *pThisThreadPool;
} DIALOG_THREAD_PARAM;
//**********************************************************************
// Variable definitions
//**********************************************************************
//**********************************************************************
// Function prototypes
//**********************************************************************
//**********************************************************************
// Function definitions
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::CThreadPool - constructor
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::CThreadPool"
CThreadPool::CThreadPool():
m_iRefCount( 0 ),
m_iTotalThreadCount( 0 ),
#ifdef WINNT
m_iNTCompletionThreadCount( 0 ),
m_fNTTimerThreadRunning( FALSE ),
m_hIOCompletionPort( NULL ),
#endif
m_fAllowThreadCountReduction( FALSE ),
m_iIntendedThreadCount( 0 ),
m_fNATHelpLoaded( FALSE ),
m_fNATHelpTimerJobSubmitted( FALSE ),
m_dwNATHelpUpdateThreadID( 0 ),
m_hStopAllThreads( NULL ),
#ifdef WIN95
m_hWinsock2SendComplete( NULL ),
m_hWinsock2ReceiveComplete( NULL ),
m_hNATHelpUpdateEvent( NULL ),
#endif
m_uReservedSocketCount( 0 )
{
m_Sig[0] = 'T';
m_Sig[1] = 'H';
m_Sig[2] = 'P';
m_Sig[3] = 'L';
memset( &m_SocketSet, 0x00, sizeof( m_SocketSet ) );
m_OutstandingReadList.Initialize();
m_OutstandingWriteList.Initialize();
m_TimerJobList.Initialize();
memset( &m_pSocketPorts, 0x00, sizeof( m_pSocketPorts ) );
#ifdef DEBUG
m_dwNumNATHelpUpdatesNotScheduled = 0;
#endif // DEBUG
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::~CThreadPool - destructor
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::~CThreadPool"
CThreadPool::~CThreadPool()
{
DNASSERT( m_iRefCount == 0 );
DNASSERT( m_iTotalThreadCount == 0 );
#ifdef WINNT
DNASSERT( m_iNTCompletionThreadCount == 0 );
DNASSERT( m_fNTTimerThreadRunning == FALSE );
DNASSERT( m_hIOCompletionPort == NULL );
#endif
DNASSERT( m_fAllowThreadCountReduction == FALSE );
DNASSERT( m_iIntendedThreadCount == 0 );
DNASSERT( m_fNATHelpLoaded == FALSE );
DNASSERT( m_fNATHelpTimerJobSubmitted == FALSE );
DNASSERT( m_dwNATHelpUpdateThreadID == 0 );
DNASSERT( m_hStopAllThreads == NULL );
#ifdef WIN95
DNASSERT( m_hWinsock2SendComplete == NULL );
DNASSERT( m_hWinsock2ReceiveComplete == NULL );
DNASSERT( m_hNATHelpUpdateEvent == NULL );
#endif
DNASSERT( m_uReservedSocketCount == 0 );
DNASSERT( m_OutstandingReadList.IsEmpty() != FALSE );
DNASSERT( m_OutstandingWriteList.IsEmpty() != FALSE );
DNASSERT( m_TimerJobList.IsEmpty() != FALSE );
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::Initialize - initialize work threads
//
// Entry: Nothing
//
// Exit: Error Code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::Initialize"
HRESULT CThreadPool::Initialize( void )
{
HRESULT hr;
//
// initialize
//
hr = DPN_OK;
//
// initialize critical sections
//
if ( DNInitializeCriticalSection( &m_Lock ) == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DebugSetCriticalSectionRecursionCount( &m_Lock, 0 );
if ( DNInitializeCriticalSection( &m_ReadDataLock ) == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
//
// Win9x has poor APC support and as part of the workaround, the read data
// lock needs to be taken twice. Adjust the recursion counts accordingly.
//
#ifdef DEBUG
DebugSetCriticalSectionRecursionCount( &m_ReadDataLock, 1 );
#endif
if ( DNInitializeCriticalSection( &m_WriteDataLock ) == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
//
// Win9x has poor APC support and as part of the workaround, the write data
// lock needs to be taken twice. Adjust the recursion counts accordingly.
//
#ifdef DEBUG
DebugSetCriticalSectionRecursionCount( &m_WriteDataLock, 1 );
#endif
if ( DNInitializeCriticalSection( &m_JobDataLock ) == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DebugSetCriticalSectionRecursionCount( &m_JobDataLock, 0 );
if ( DNInitializeCriticalSection( &m_TimerDataLock ) == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DebugSetCriticalSectionRecursionCount( &m_TimerDataLock, 1 );
//
// initialize job queue
//
if ( m_JobQueue.Initialize() == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
//
// initialize pools
//
#ifndef USE_THREADLOCALPOOLS
// pool of IP read requests
m_IPReadIODataPool.Initialize( CReadIOData::ReadIOData_Alloc,
CReadIOData::ReadIOData_Get,
CReadIOData::ReadIOData_Release,
CReadIOData::ReadIOData_Dealloc
);
// pool of IPX read requests
m_IPXReadIODataPool.Initialize( CReadIOData::ReadIOData_Alloc,
CReadIOData::ReadIOData_Get,
CReadIOData::ReadIOData_Release,
CReadIOData::ReadIOData_Dealloc
);
// pool of write requests
m_WriteIODataPool.Initialize( CWriteIOData::WriteIOData_Alloc,
CWriteIOData::WriteIOData_Get,
CWriteIOData::WriteIOData_Release,
CWriteIOData::WriteIOData_Dealloc
);
#endif // ! USE_THREADLOCALPOOLS
// job pool
if ( FPM_Initialize( &m_JobPool, // pointer to pool
sizeof( THREAD_POOL_JOB ), // size of pool entry
WorkThreadJob_Alloc, // function called on pool entry initial allocation
WorkThreadJob_Get, // function called on entry extraction from pool
WorkThreadJob_Release, // function called on entry return to pool
WorkThreadJob_Dealloc // function called on entry free
) == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
// enum entry pool
if ( FPM_Initialize( &m_TimerEntryPool, // pointer to pool
sizeof( TIMER_OPERATION_ENTRY ), // size of pool entry
TimerEntry_Alloc, // function called on pool entry initial allocation
TimerEntry_Get, // function called on entry extraction from pool
TimerEntry_Release, // function called on entry return to pool
TimerEntry_Dealloc // function called on entry free
) == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
//
// Create event to stop all threads. Win9x needs this to stop processing
// and the NT enum thread uses this to stop processing
//
DNASSERT( m_hStopAllThreads == NULL );
m_hStopAllThreads = CreateEvent( NULL, // pointer to security (none)
TRUE, // manual reset
FALSE, // start unsignalled
NULL ); // pointer to name (none)
if ( m_hStopAllThreads == NULL )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to create event to stop all threads!" );
DisplayErrorCode( 0, dwError );
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DNASSERT( m_fAllowThreadCountReduction == FALSE );
m_fAllowThreadCountReduction = TRUE;
//
// Attempt to load the NAT helper, unless all NAT/firewall traversal is disabled.
//
DNASSERT( m_fNATHelpLoaded == FALSE );
if ((! g_fDisableDPNHGatewaySupport) || (! g_fDisableDPNHFirewallSupport))
{
if ( LoadNATHelp() == FALSE )
{
DPFX(DPFPREP, 0, "Failed to load NAT Help, continuing." );
}
else
{
m_fNATHelpLoaded = TRUE;
}
}
else
{
DPFX(DPFPREP, 0, "Not loading NAT Help." );
}
//
// OS-specific initialization
//
#ifdef WINNT
//
// WinNT
//
hr = WinNTInit();
if ( hr != DPN_OK )
{
goto Failure;
}
#else // WIN95
//
// Windows 9x
//
hr = Win9xInit();
if ( hr != DPN_OK )
{
goto Failure;
}
#endif
Exit:
if ( hr != DPN_OK )
{
DPFX(DPFPREP, 0, "Problem with CreateWorkThreads" );
DisplayDNError( 0, hr );
}
return hr;
Failure:
if ( IsNATHelpLoaded() != FALSE )
{
UnloadNATHelp();
m_fNATHelpLoaded = FALSE;
}
StopAllThreads();
Deinitialize();
goto Exit;
}
//**********************************************************************
#ifdef WINNT
//**********************************************************************
// ------------------------------
// CThreadPool::WinNTInit - initialize WinNT components
//
// Entry: Nothing
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::WinNTInit"
HRESULT CThreadPool::WinNTInit( void )
{
HRESULT hr;
//
// initialize
//
hr = DPN_OK;
DNASSERT( m_hIOCompletionPort == NULL );
m_hIOCompletionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, // don't associate a file handle yet
NULL, // handle of existing completion port (none)
NULL, // completion key for callback (none)
0 // number of concurent threads (0 = use number of processors)
);
if ( m_hIOCompletionPort == NULL )
{
DWORD dwError;
hr = DPNERR_OUTOFMEMORY;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Could not create NT IOCompletionPort!" );
DisplayErrorCode( 0, dwError );
goto Failure;
}
DPFX(DPFPREP, 7, "SetIntendedThreadCount %i", g_iThreadCount);
SetIntendedThreadCount( g_iThreadCount );
Exit:
return hr;
Failure:
DPFX(DPFPREP, 0, "Failed WinNT initialization!" );
DisplayDNError( 0, hr );
goto Exit;
}
//**********************************************************************
#endif // WINNT
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::Win9xInit - initialize Win9x components
//
// Entry: Nothing
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::Win9xInit"
HRESULT CThreadPool::Win9xInit( void )
{
HRESULT hr;
//
// initialize
//
hr = DPN_OK;
//
// Win9x requires completion events for Winsock2. Always allocate the
// events even though the they might not be used.
//
DNASSERT( m_hWinsock2SendComplete == NULL );
m_hWinsock2SendComplete = CreateEvent( NULL, // pointer to security (none)
TRUE, // manual reset
FALSE, // start unsignalled
NULL // pointer to name (none)
);
if ( m_hWinsock2SendComplete == NULL )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to create event for Winsock2Send!" );
DisplayErrorCode( 0, dwError );
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DNASSERT( m_hWinsock2ReceiveComplete == NULL );
m_hWinsock2ReceiveComplete = CreateEvent( NULL, // pointer to security (none)
TRUE, // manual reset
FALSE, // start unsignalled
NULL // pointer to name (none)
);
if ( m_hWinsock2ReceiveComplete == NULL )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to create event for Winsock2Receive!" );
DisplayErrorCode( 0, dwError );
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
//
// Event gets created whether we use NAT Help or not. This simplifies code.
//
DNASSERT( m_hNATHelpUpdateEvent == NULL );
m_hNATHelpUpdateEvent = CreateEvent( NULL, // pointer to security (none)
TRUE, // manual reset
FALSE, // start unsignalled
NULL // pointer to name (none)
);
if ( m_hNATHelpUpdateEvent == NULL )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to create event for NAT Help update!" );
DisplayErrorCode( 0, dwError );
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DPFX(DPFPREP, 7, "Created NAT Help update event 0x%p.", m_hNATHelpUpdateEvent);
DPFX(DPFPREP, 7, "SetIntendedThreadCount %i", g_iThreadCount);
SetIntendedThreadCount( g_iThreadCount );
Exit:
return hr;
Failure:
DPFX(DPFPREP, 0, "Failed Win9x Initialization!" );
DisplayDNError( 0, hr );
goto Exit;
}
//**********************************************************************
#endif // WIN95
#ifdef WINNT
//**********************************************************************
// ------------------------------
// CThreadPool::StartNTCompletionThread - start a WinNT completion thread
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::StartNTCompletionThread"
void CThreadPool::StartNTCompletionThread( void )
{
HANDLE hThread;
DWORD dwThreadID;
IOCOMPLETION_THREAD_DATA *pIOCompletionThreadData;
pIOCompletionThreadData = static_cast<IOCOMPLETION_THREAD_DATA*>( DNMalloc( sizeof( *pIOCompletionThreadData ) ) );
if ( pIOCompletionThreadData != NULL )
{
//
// assume that a thread will be created
//
IncrementActiveNTCompletionThreadCount();
pIOCompletionThreadData->pThisThreadPool = this;
hThread = NULL;
hThread = CreateThread( NULL, // pointer to security attributes (none)
0, // stack size (default)
WinNTIOCompletionThread, // thread function
pIOCompletionThreadData, // thread parameter
0, // start thread immediately
&dwThreadID // pointer to thread ID destination
);
if ( hThread != NULL )
{
//
// close thread handle because it's no longer needed
//
DPFX(DPFPREP, 8, "Creating I/O completion thread: 0x%x\tTotal Thread Count: %d\tNT Completion Thread Count: %d", dwThreadID, ThreadCount(), NTCompletionThreadCount() );
if ( CloseHandle( hThread ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem creating thread for I/O completion port" );
DisplayErrorCode( 0, dwError );
}
}
else
{
DWORD dwError;
//
// failed to create thread, decrement active thread counts and
// report error
//
DecrementActiveNTCompletionThreadCount();
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to create I/O completion thread!" );
DisplayErrorCode( 0, dwError );
DNFree( pIOCompletionThreadData );
}
}
}
//**********************************************************************
#endif // WINNT
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::StartPrimaryWin9xIOThread - start the primary Win9x thread
//
// Entry: Nothing
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::StartPrimaryWin9xIOThread"
void CThreadPool::StartPrimaryWin9xIOThread( void )
{
HANDLE hPrimaryThread;
DWORD dwPrimaryThreadID;
WIN9X_THREAD_DATA *pPrimaryThreadInput;
//
// initialize
//
hPrimaryThread = NULL;
pPrimaryThreadInput = NULL;
//
// create parameters to worker threads
//
pPrimaryThreadInput = static_cast<WIN9X_THREAD_DATA*>( DNMalloc( sizeof( *pPrimaryThreadInput ) ) );
if ( pPrimaryThreadInput == NULL )
{
DPFX(DPFPREP, 0, "Problem allocating memory for primary Win9x thread!" );
goto Failure;
}
memset( pPrimaryThreadInput, 0x00, sizeof( *pPrimaryThreadInput ) );
pPrimaryThreadInput->pThisThreadPool = this;
//
// assume that the thread will be created
//
IncrementActiveThreadCount();
//
// Create one worker thread and boost its priority. If the primary thread
// can be created and boosted, create a secondary thread. Do not create a
// secondary thread if the primary could not be boosted because the system
// is probably low on resources.
//
hPrimaryThread = CreateThread( NULL, // pointer to security attributes (none)
0, // stack size (default)
PrimaryWin9xThread, // pointer to thread function
pPrimaryThreadInput, // pointer to input parameter
0, // let it run
&dwPrimaryThreadID // pointer to destination of thread ID
);
if ( hPrimaryThread == NULL )
{
DWORD dwError;
//
// Failed to create thread, decrement active thread count and report
// error.
//
DecrementActiveThreadCount();
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem creating primary Win9x thread!" );
DisplayErrorCode( 0, dwError );
goto Failure;
}
pPrimaryThreadInput = NULL;
DPFX(DPFPREP, 8, "Created primary Win9x thread: 0x%x\tTotal Thread Count: %d", dwPrimaryThreadID, ThreadCount() );
DNASSERT( hPrimaryThread != NULL );
#ifdef ADJUST_THREAD_PRIORITY
if ( SetThreadPriority( hPrimaryThread, THREAD_PRIORITY_ABOVE_NORMAL ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to boost priority of primary Win9x thread! Ignoring." );
DisplayErrorCode( 0, dwError );
//
// Not fatal, just continue.
//
}
#endif // ADJUST_THREAD_PRIORITY
Exit:
if ( pPrimaryThreadInput != NULL )
{
DNFree( pPrimaryThreadInput );
pPrimaryThreadInput = NULL;
}
if ( hPrimaryThread != NULL )
{
if ( CloseHandle( hPrimaryThread ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem closing Win9x thread handle!" );
DisplayErrorCode( 0, dwError );
}
hPrimaryThread = NULL;
}
return;
Failure:
goto Exit;
}
//**********************************************************************
#endif // WIN95
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::StartSecondaryWin9xIOThread - start a secondary Win9x thread
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::StartSecondaryWin9xIOThread"
void CThreadPool::StartSecondaryWin9xIOThread( void )
{
WIN9X_THREAD_DATA *pSecondaryThreadInput;
pSecondaryThreadInput = static_cast<WIN9X_THREAD_DATA*>( DNMalloc( sizeof( *pSecondaryThreadInput ) ) );
if ( pSecondaryThreadInput != NULL )
{
HANDLE hSecondaryThread;
DWORD dwSecondaryThreadID;
memset( pSecondaryThreadInput, 0x00, sizeof( *pSecondaryThreadInput ) );
pSecondaryThreadInput->pThisThreadPool = this;
IncrementActiveThreadCount();
hSecondaryThread = CreateThread( NULL, // pointer to security attributes (none)
0, // stack size (default)
SecondaryWin9xThread, // pointer to thread function
pSecondaryThreadInput, // pointer to input parameter
0, // let it run
&dwSecondaryThreadID // pointer to destination of thread ID
);
if ( hSecondaryThread != NULL )
{
DPFX(DPFPREP, 8, "Created secondary Win9x thread: 0x%x\tTotal Thread Count: %d", dwSecondaryThreadID, ThreadCount() );
pSecondaryThreadInput = NULL;
DNASSERT( hSecondaryThread != NULL );
#ifdef ADJUST_THREAD_PRIORITY
if ( SetThreadPriority( hSecondaryThread, THREAD_PRIORITY_ABOVE_NORMAL ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to boost priority of secondary Win9x thread!" );
DisplayErrorCode( 0, dwError );
//
// Not fatal, just continue.
//
}
#endif // ADJUST_THREAD_PRIORITY
if ( CloseHandle( hSecondaryThread ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to close handle when starting secondary Win9x thread!" );
DisplayErrorCode( 0, dwError );
}
hSecondaryThread = NULL;
}
else
{
//
// thread startup failed, decrement active thread count
//
DecrementActiveThreadCount();
}
if ( pSecondaryThreadInput != NULL )
{
DNFree( pSecondaryThreadInput );
pSecondaryThreadInput = NULL;
}
}
}
//**********************************************************************
#endif // WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::StopAllThreads - stop all work threads
//
// Entry: Nothing
//
// Exit: Nothing
//
// Note: This function blocks until all threads complete!
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::StopAllThreads"
void CThreadPool::StopAllThreads( void )
{
//
// stop all non-I/O completion threads
//
if ( m_hStopAllThreads != NULL )
{
if ( SetEvent( m_hStopAllThreads ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to set event to stop all threads!" );
DisplayErrorCode( 0, dwError );
}
}
//
// If we're running on NT submit enough jobs to stop all threads.
//
#ifdef WINNT
UINT_PTR uIndex;
uIndex = NTCompletionThreadCount();
while ( uIndex > 0 )
{
uIndex--;
if ( PostQueuedCompletionStatus( m_hIOCompletionPort, // handle of completion port
0, // number of bytes transferred
IO_COMPLETION_KEY_SP_CLOSE, // completion key
NULL // pointer to overlapped structure (none)
) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem submitting Stop job to IO completion port!" );
DisplayErrorCode( 0, dwError );
}
}
#endif
//
// check for outstanding threads (no need to lock thread pool count)
//
DPFX(DPFPREP, 8, "Number of outstanding threads: %d", ThreadCount() );
while ( ThreadCount() != 0 )
{
DPFX(DPFPREP, 8, "Waiting for %d threads to quit.", ThreadCount() );
SleepEx( WORK_THREAD_CLOSE_SLEEP_TIME, TRUE );
}
DNASSERT( ThreadCount() == 0 );
m_iTotalThreadCount = 0;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::CancelOutstandingJobs - cancel outstanding jobs
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::CancelOutstandingJobs"
void CThreadPool::CancelOutstandingJobs( void )
{
DNASSERT( ThreadCount() == 0 );
m_JobQueue.Lock();
while ( m_JobQueue.IsEmpty() == FALSE )
{
THREAD_POOL_JOB *pJob;
pJob = m_JobQueue.DequeueJob();
DNASSERT( pJob != NULL );
DNASSERT( pJob->pCancelFunction != NULL );
pJob->pCancelFunction( pJob );
pJob->JobType = JOB_UNINITIALIZED;
m_JobPool.Release( &m_JobPool, pJob );
};
m_JobQueue.Unlock();
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::CancelOutstandingIO - cancel outstanding IO
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::CancelOutstandingIO"
void CThreadPool::CancelOutstandingIO( void )
{
CBilink *pTemp;
DPFX(DPFPREP, 5, "(0x%p) Enter", this);
DNASSERT( ThreadCount() == 0 );
//
// stop any receives with the notification that Winsock is shutting down.
//
pTemp = m_OutstandingReadList.GetNext();
while ( pTemp != &m_OutstandingReadList )
{
CReadIOData *pReadData;
pReadData = CReadIOData::ReadDataFromBilink( pTemp );
pTemp = pTemp->GetNext();
pReadData->m_OutstandingReadListLinkage.RemoveFromList();
DEBUG_ONLY( if ( pReadData->m_fRetainedByHigherLayer != FALSE )
{
DNASSERT( FALSE );
}
);
#ifdef WIN95
DNASSERT( pReadData->Win9xOperationPending() != FALSE );
pReadData->SetWin9xOperationPending( FALSE );
#endif
pReadData->m_ReceiveWSAReturn = WSAESHUTDOWN;
pReadData->m_dwOverlappedBytesReceived = 0;
pReadData->SocketPort()->CancelReceive( pReadData );
}
//
// stop any pending writes with the notification that the user cancelled it.
//
pTemp = m_OutstandingWriteList.GetNext();
while ( pTemp != &m_OutstandingWriteList )
{
CWriteIOData *pWriteData;
CSocketPort *pSocketPort;
pWriteData = CWriteIOData::WriteDataFromBilink( pTemp );
pTemp = pTemp->GetNext();
pWriteData->m_OutstandingWriteListLinkage.RemoveFromList();
#ifdef WIN95
DNASSERT( pWriteData->Win9xOperationPending() != FALSE );
pWriteData->SetWin9xOperationPending( FALSE );
#endif
pSocketPort = pWriteData->SocketPort();
pSocketPort->SendComplete( pWriteData, DPNERR_USERCANCEL );
pSocketPort->DecRef();
}
while ( m_JobQueue.IsEmpty() == FALSE )
{
THREAD_POOL_JOB *pJob;
pJob = m_JobQueue.DequeueJob();
DNASSERT( pJob != NULL );
DNASSERT( pJob->pCancelFunction != NULL );
pJob->pCancelFunction( pJob );
pJob->JobType = JOB_UNINITIALIZED;
m_JobPool.Release( &m_JobPool, pJob );
};
DPFX(DPFPREP, 6, "(0x%p) Leave", this);
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::ReturnSelfToPool - return this object to the pool
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::ReturnSelfToPool"
void CThreadPool::ReturnSelfToPool( void )
{
Deinitialize();
ReturnThreadPool( this );
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::Deinitialize - destroy work threads
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::Deinitialize"
void CThreadPool::Deinitialize( void )
{
DNASSERT( m_JobQueue.IsEmpty() != FALSE );
if ( IsNATHelpLoaded() )
{
if ( IsNATHelpTimerJobSubmitted() )
{
StopTimerJob( this, DPNERR_USERCANCEL );
m_fNATHelpTimerJobSubmitted = FALSE;
}
UnloadNATHelp();
m_fNATHelpLoaded = FALSE;
}
#ifdef WINNT
if ( m_hIOCompletionPort != NULL )
{
if ( CloseHandle( m_hIOCompletionPort ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem closing handle to I/O completion port!" );
DisplayErrorCode( 0, dwError );
}
m_hIOCompletionPort = NULL;
}
#endif
//
// close StopAllThreads handle
//
if ( m_hStopAllThreads != NULL )
{
if ( CloseHandle( m_hStopAllThreads ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to close StopAllThreads handle!" );
DisplayErrorCode( 0, dwError );
}
m_hStopAllThreads = NULL;
}
#ifdef WIN95
//
// close handles for I/O events
//
if ( m_hWinsock2SendComplete != NULL )
{
if ( CloseHandle( m_hWinsock2SendComplete ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem closing Winsock2SendComplete handle!" );
DisplayErrorCode( 0, dwError );
}
m_hWinsock2SendComplete = NULL;
}
if ( m_hWinsock2ReceiveComplete != NULL )
{
if ( CloseHandle( m_hWinsock2ReceiveComplete ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem closing Winsock2ReceiveComplete handle!" );
DisplayErrorCode( 0, dwError );
}
m_hWinsock2ReceiveComplete = NULL;
}
if ( m_hNATHelpUpdateEvent != NULL )
{
if ( CloseHandle( m_hNATHelpUpdateEvent ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem closing NAT HelpUpdate handle!" );
DisplayErrorCode( 0, dwError );
}
m_hNATHelpUpdateEvent = NULL;
}
#endif // WIN95
//
// deinitialize pools
//
// timer entry pool
FPM_Deinitialize( &m_TimerEntryPool );
// job pool
FPM_Deinitialize( &m_JobPool );
// write data pool
DNASSERT( m_OutstandingWriteList.IsEmpty() != FALSE );
#ifndef USE_THREADLOCALPOOLS
m_WriteIODataPool.Deinitialize();
#endif // ! USE_THREADLOCALPOOLS
// read data pool
DNASSERT( m_OutstandingReadList.IsEmpty() != FALSE );
#ifndef USE_THREADLOCALPOOLS
m_IPReadIODataPool.Deinitialize();
m_IPXReadIODataPool.Deinitialize();
#endif // ! USE_THREADLOCALPOOLS
//
// deinitialize job queue
//
m_JobQueue.Deinitialize();
DNDeleteCriticalSection( &m_TimerDataLock );
DNDeleteCriticalSection( &m_JobDataLock );
DNDeleteCriticalSection( &m_WriteDataLock );
DNDeleteCriticalSection( &m_ReadDataLock );
DNDeleteCriticalSection( &m_Lock );
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::StopAllIO - stop all IO from the thread pool
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::StopAllIO"
void CThreadPool::StopAllIO( void )
{
//
// request that all threads stop and then cycle our timeslice to
// allow the threads a chance for cleanup
//
m_fAllowThreadCountReduction = FALSE;
DPFX(DPFPREP, 7, "SetIntendedThreadCount 0");
SetIntendedThreadCount( 0 );
StopAllThreads();
while ( m_JobQueue.IsEmpty() == FALSE )
{
THREAD_POOL_JOB *pThreadPoolJob;
pThreadPoolJob = GetWorkItem();
DNASSERT( pThreadPoolJob != NULL );
pThreadPoolJob->pCancelFunction( pThreadPoolJob );
pThreadPoolJob->JobType = JOB_UNINITIALIZED;
m_JobPool.Release( &m_JobPool, pThreadPoolJob );
}
//
// Now that all of the threads are stopped, clean up any outstanding I/O.
// this can be done without taking any locks
//
CancelOutstandingIO();
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::GetNewReadIOData - get new read request from pool
//
// Entry: Pointer to context
//
// Exit: Pointer to new read request
// NULL = out of memory
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::GetNewReadIOData"
CReadIOData *CThreadPool::GetNewReadIOData( READ_IO_DATA_POOL_CONTEXT *const pContext )
{
#ifdef USE_THREADLOCALPOOLS
CContextFixedTLPool<CReadIOData, READ_IO_DATA_POOL_CONTEXT> * pPool;
BOOL fResult;
CReadIOData * pTempReadData;
DNASSERT( pContext != NULL );
//
// initialize
//
pContext->pThreadPool = this;
//
// Get the pool pointer.
//
switch ( pContext->SPType )
{
//
// IP
//
case TYPE_IP:
{
GET_THREADLOCALPTR( WSockThreadLocalPools,
pIPReadIODataPool,
&pPool );
break;
}
//
// IPX
//
case TYPE_IPX:
{
GET_THREADLOCALPTR( WSockThreadLocalPools,
pIPXReadIODataPool,
&pPool );
break;
}
//
// unknown SP type (should never be here!)
//
default:
{
DNASSERT( FALSE );
goto Exit;
break;
}
}
//
// Create the pool if it didn't exist.
//
if ( pPool == NULL )
{
pPool = new CContextFixedTLPool<CReadIOData, READ_IO_DATA_POOL_CONTEXT>;
if ( pPool == NULL )
{
pTempReadData = NULL;
goto Exit;
}
//
// Try to initialize the pool.
//
if ( pContext->SPType == TYPE_IP )
{
fResult = pPool->Initialize( g_pGlobalIPReadIODataPool,
CReadIOData::ReadIOData_Alloc,
CReadIOData::ReadIOData_Get,
CReadIOData::ReadIOData_Release,
CReadIOData::ReadIOData_Dealloc
);
}
else
{
fResult = pPool->Initialize( g_pGlobalIPXReadIODataPool,
CReadIOData::ReadIOData_Alloc,
CReadIOData::ReadIOData_Get,
CReadIOData::ReadIOData_Release,
CReadIOData::ReadIOData_Dealloc
);
}
if (! fResult)
{
//
// Initializing pool failed, delete it and abort.
//
delete pPool;
pTempReadData = NULL;
goto Exit;
}
//
// Associate the pool with this thread.
//
if ( pContext->SPType == TYPE_IP )
{
SET_THREADLOCALPTR( WSockThreadLocalPools,
pIPReadIODataPool,
pPool,
&fResult );
}
else
{
SET_THREADLOCALPTR( WSockThreadLocalPools,
pIPXReadIODataPool,
pPool,
&fResult );
}
if (! fResult)
{
//
// Associating pool with thread failed, de-initialize it, delete it,
// and abort.
//
pPool->Deinitialize();
delete pPool;
pTempReadData = NULL;
goto Exit;
}
}
//
// Get an item out of the pool.
//
pTempReadData = pPool->Get( pContext );
if ( pTempReadData == NULL )
{
DPFX(DPFPREP, 0, "Failed to get new ReadIOData from pool!" );
goto Exit;
}
//
// we have data, immediately add a reference to it
//
pTempReadData->AddRef();
DNASSERT( pTempReadData->m_pSourceSocketAddress != NULL );
//
// now that the read data is initialized, add it to the
// unbound list
//
LockReadData();
pTempReadData->m_OutstandingReadListLinkage.InsertBefore( &m_OutstandingReadList );
UnlockReadData();
Exit:
return pTempReadData;
#else // ! USE_THREADLOCALPOOLS
CReadIOData *pTempReadData;
DNASSERT( pContext != NULL );
//
// initialize
//
pTempReadData = NULL;
pContext->pThreadPool = this;
LockReadData();
switch ( pContext->SPType )
{
//
// IP
//
case TYPE_IP:
{
pTempReadData = m_IPReadIODataPool.Get( pContext );
break;
}
//
// IPX
//
case TYPE_IPX:
{
pTempReadData = m_IPXReadIODataPool.Get( pContext );
break;
}
//
// unknown SP type (should never be here!)
//
default:
{
DNASSERT( FALSE );
break;
}
}
if ( pTempReadData == NULL )
{
DPFX(DPFPREP, 0, "Failed to get new ReadIOData from pool!" );
goto Failure;
}
//
// we have data, immediately add a reference to it
//
pTempReadData->AddRef();
DNASSERT( pTempReadData->m_pSourceSocketAddress != NULL );
//
// now that the read data is initialized, add it to the
// unbound list
//
pTempReadData->m_OutstandingReadListLinkage.InsertBefore( &m_OutstandingReadList );
Exit:
UnlockReadData();
return pTempReadData;
Failure:
if ( pTempReadData != NULL )
{
pTempReadData->DecRef();
pTempReadData = NULL;
}
goto Exit;
#endif // ! USE_THREADLOCALPOOLS
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::ReturnReadIOData - return read data item to pool
//
// Entry: Pointer to read data
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::ReturnReadIOData"
void CThreadPool::ReturnReadIOData( CReadIOData *const pReadData )
{
#ifdef USE_THREADLOCALPOOLS
CContextFixedTLPool<CReadIOData, READ_IO_DATA_POOL_CONTEXT> * pPool;
BOOL fResult;
DNASSERT( pReadData != NULL );
DNASSERT( pReadData->m_pSourceSocketAddress != NULL );
//
// remove this item from the unbound list
//
LockReadData();
pReadData->m_OutstandingReadListLinkage.RemoveFromList();
UnlockReadData();
DNASSERT( pReadData->m_OutstandingReadListLinkage.IsEmpty() != FALSE );
//
// Get the pool pointer.
//
switch ( pReadData->m_pSourceSocketAddress->GetFamily() )
{
//
// IP
//
case AF_INET:
{
GET_THREADLOCALPTR( WSockThreadLocalPools,
pIPReadIODataPool,
&pPool );
break;
}
//
// IPX
//
case AF_IPX:
{
GET_THREADLOCALPTR( WSockThreadLocalPools,
pIPXReadIODataPool,
&pPool );
break;
}
//
// unknown type (shouldn't be here!)
//
default:
{
DNASSERT( FALSE );
//goto Exit;
break;
}
}
//
// Create the pool if it didn't exist.
//
if ( pPool == NULL )
{
pPool = new CContextFixedTLPool<CReadIOData, READ_IO_DATA_POOL_CONTEXT>;
if ( pPool == NULL )
{
//
// Couldn't create this thread's pool, just release the item
// without the pool.
//
CContextFixedTLPool<CReadIOData, READ_IO_DATA_POOL_CONTEXT>::ReleaseWithoutPool( pReadData, CReadIOData::ReadIOData_Release, CReadIOData::ReadIOData_Dealloc );
return;
}
//
// Try to initialize the pool.
//
if ( pReadData->m_pSourceSocketAddress->GetFamily() == AF_INET )
{
fResult = pPool->Initialize( g_pGlobalIPReadIODataPool,
CReadIOData::ReadIOData_Alloc,
CReadIOData::ReadIOData_Get,
CReadIOData::ReadIOData_Release,
CReadIOData::ReadIOData_Dealloc
);
}
else
{
fResult = pPool->Initialize( g_pGlobalIPXReadIODataPool,
CReadIOData::ReadIOData_Alloc,
CReadIOData::ReadIOData_Get,
CReadIOData::ReadIOData_Release,
CReadIOData::ReadIOData_Dealloc
);
}
if (! fResult)
{
//
// Initializing this thread's pool failed, just release the
// item without the pool, and destroy the pool object that
// couldn't be used.
//
CContextFixedTLPool<CReadIOData, READ_IO_DATA_POOL_CONTEXT>::ReleaseWithoutPool( pReadData, CReadIOData::ReadIOData_Release, CReadIOData::ReadIOData_Dealloc );
delete pPool;
return;
}
//
// Associate the pool with this thread.
//
if ( pReadData->m_pSourceSocketAddress->GetFamily() == AF_INET )
{
SET_THREADLOCALPTR( WSockThreadLocalPools,
pIPReadIODataPool,
pPool,
&fResult );
}
else
{
SET_THREADLOCALPTR( WSockThreadLocalPools,
pIPXReadIODataPool,
pPool,
&fResult );
}
if ( ! fResult )
{
//
// Couldn't store this thread's pool, just release the item
// without the pool, plus de-initialize and destroy the pool
// object that couldn't be used.
//
CContextFixedTLPool<CReadIOData, READ_IO_DATA_POOL_CONTEXT>::ReleaseWithoutPool( pReadData, CReadIOData::ReadIOData_Release, CReadIOData::ReadIOData_Dealloc );
pPool->Deinitialize();
delete pPool;
return;
}
}
pPool->Release( pReadData );
#else // ! USE_THREADLOCALPOOLS
DNASSERT( pReadData != NULL );
DNASSERT( pReadData->m_pSourceSocketAddress != NULL );
//
// remove this item from the unbound list and return it to the pool
//
LockReadData();
pReadData->m_OutstandingReadListLinkage.RemoveFromList();
DNASSERT( pReadData->m_OutstandingReadListLinkage.IsEmpty() != FALSE );
switch ( pReadData->m_pSourceSocketAddress->GetFamily() )
{
//
// IP
//
case AF_INET:
{
m_IPReadIODataPool.Release( pReadData );
break;
}
//
// IPX
//
case AF_IPX:
{
m_IPXReadIODataPool.Release( pReadData );
break;
}
//
// unknown type (shouldn't be here!)
//
default:
{
DNASSERT( FALSE );
break;
}
}
UnlockReadData();
#endif // ! USE_THREADLOCALPOOLS
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::ProcessTimerJobs - process timed jobs
//
// Entry: Pointer to job list
// Pointer to destination for time of next job
//
// Exit: Boolean indicating active timer jobs exist
// TRUE = there are active jobs
// FALSE = there are no active jobs
//
// Notes: The input job queue is expected to be locked for the duration
// of this function call!
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::ProcessTimerJobs"
BOOL CThreadPool::ProcessTimerJobs( const CBilink *const pJobList, DN_TIME *const pNextJobTime )
{
BOOL fReturn;
CBilink *pWorkingEntry;
INT_PTR iActiveTimerJobCount;
DN_TIME CurrentTime;
DNASSERT( pJobList != NULL );
DNASSERT( pNextJobTime != NULL );
//
// Initialize. Set the next job time to be infinitely far in the future
// so this thread will wake up for any jobs that need to completed before
// then.
//
fReturn = FALSE;
DBG_CASSERT( OFFSETOF( TIMER_OPERATION_ENTRY, Linkage ) == 0 );
pWorkingEntry = pJobList->GetNext();
iActiveTimerJobCount = 0;
memset( pNextJobTime, 0xFF, sizeof( *pNextJobTime ) );
DNTimeGet( &CurrentTime );
DPFX(DPFPREP, 8, "Threadpool 0x%p processing timer jobs, current offset time = %u.",
this, CurrentTime.Time32.TimeLow);
//
// loop through all timer items
//
while ( pWorkingEntry != pJobList )
{
TIMER_OPERATION_ENTRY *pTimerEntry;
BOOL fJobActive;
pTimerEntry = TIMER_OPERATION_ENTRY::TimerOperationFromLinkage( pWorkingEntry );
pWorkingEntry = pWorkingEntry->GetNext();
fJobActive = ProcessTimedOperation( pTimerEntry, &CurrentTime, pNextJobTime );
DNASSERT( ( fJobActive == FALSE ) || ( fJobActive == TRUE ) );
fReturn |= fJobActive;
if ( fJobActive == FALSE )
{
RemoveTimerOperationEntry( pTimerEntry, DPN_OK );
}
}
DNASSERT( ( fReturn == FALSE ) || ( fReturn == TRUE ) );
return fReturn;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::ProcessTimedOperation - process a timed operation
//
// Entry: Pointer to job information
// Pointer to current time
// Pointer to time to be updated
//
// Exit: Boolean indicating that the job is still active
// TRUE = operation active
// FALSE = operation not active
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::ProcessTimedOperation"
BOOL CThreadPool::ProcessTimedOperation( TIMER_OPERATION_ENTRY *const pTimedJob,
const DN_TIME *const pCurrentTime,
DN_TIME *const pNextJobTime )
{
BOOL fEnumActive;
DNASSERT( pTimedJob != NULL );
DNASSERT( pCurrentTime != NULL );
DNASSERT( pNextJobTime != NULL );
DPFX(DPFPREP, 8, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p)",
this, pTimedJob, pCurrentTime, pNextJobTime);
//
// Assume that this enum will remain active. If we retire this enum, this
// value will be reset.
//
fEnumActive = TRUE;
//
// If this enum has completed sending enums and is waiting only
// for responses, decrement the wait time (assuming it's not infinite)
// and remove the enum if the we've exceeded its wait time.
//
if ( pTimedJob->uRetryCount == 0 )
{
if ( DNTimeCompare( &pTimedJob->IdleTimeout, pCurrentTime ) <= 0 )
{
fEnumActive = FALSE;
}
else
{
//
// This enum isn't complete, check to see if it's the next enum
// to need service.
//
if ( DNTimeCompare( &pTimedJob->IdleTimeout, pNextJobTime ) < 0 )
{
DBG_CASSERT( sizeof( *pNextJobTime ) == sizeof( pTimedJob->IdleTimeout ) );
memcpy( pNextJobTime, &pTimedJob->IdleTimeout, sizeof( *pNextJobTime ) );
}
}
}
else
{
//
// This enum is still sending. Determine if it's time to perform the job
// and adjust the wakeup time if appropriate.
//
if ( DNTimeCompare( &pTimedJob->NextRetryTime, pCurrentTime ) <= 0 )
{
//
// If the job should be performed immediately do so. Otherwise, just
// sit tight until the retry interval elapses.
//
if ( pTimedJob->fPerformImmediately )
{
#ifdef DEBUG
DN_TIME Delay;
DNTimeSubtract( pCurrentTime, &pTimedJob->NextRetryTime, &Delay );
DPFX(DPFPREP, 7, "Threadpool 0x%p performing timed job 0x%p at time offset %u, approximately %u ms after intended time offset of %u.",
this, pTimedJob, pCurrentTime->Time32.TimeLow,
Delay.Time32.TimeLow, pTimedJob->NextRetryTime.Time32.TimeLow);
#endif // DEBUG
//
// Timeout, execute this timed item
//
pTimedJob->pTimerCallback( pTimedJob->pContext, &pTimedJob->RetryInterval );
//
// If this job isn't running forever, decrement the retry count.
// If there are no more retries, set up wait time. If the job
// is waiting forever, set max wait timeout.
//
if ( pTimedJob->fRetryForever == FALSE )
{
pTimedJob->uRetryCount--;
if ( pTimedJob->uRetryCount == 0 )
{
if ( pTimedJob->fIdleWaitForever == FALSE )
{
//
// Compute stopping time for this job's 'Timeout' phase and
// see if this will be the next job to need service. ASSERT
// if the math wraps.
//
DNTimeAdd( &pTimedJob->IdleTimeout, pCurrentTime, &pTimedJob->IdleTimeout );
DNASSERT( pTimedJob->IdleTimeout.Time32.TimeHigh >= pCurrentTime->Time32.TimeHigh );
if ( DNTimeCompare( &pTimedJob->IdleTimeout, pNextJobTime ) < 0 )
{
DBG_CASSERT( sizeof( *pNextJobTime ) == sizeof( pTimedJob->IdleTimeout ) );
memcpy( pNextJobTime, &pTimedJob->IdleTimeout, sizeof( *pNextJobTime ) );
}
}
else
{
//
// We're waiting forever for enum returns. ASSERT that we
// have the maximum timeout and don't bother checking to see
// if this will be the next enum to need service (it'll never
// need service). The following code needs to be revisited for
// 64-bits.
//
DNASSERT( pTimedJob->IdleTimeout.Time32.TimeLow == -1 );
DNASSERT( pTimedJob->IdleTimeout.Time32.TimeHigh == -1 );
}
goto SkipNextRetryTimeComputation;
}
} // end if (don't retry forever)
}
else
{
DPFX(DPFPREP, 8, "Job 0x%p not performed immediately, waiting until retry time (%u ms).",
pTimedJob, pTimedJob->RetryInterval.Time32.TimeLow);
pTimedJob->fPerformImmediately = TRUE;
}
DNTimeAdd( pCurrentTime, &pTimedJob->RetryInterval, &pTimedJob->NextRetryTime );
}
//
// is this the next timed job to fire?
//
if ( DNTimeCompare( &pTimedJob->NextRetryTime, pNextJobTime ) < 0 )
{
DBG_CASSERT( sizeof( *pNextJobTime ) == sizeof( pTimedJob->NextRetryTime ) );
memcpy( pNextJobTime, &pTimedJob->NextRetryTime, sizeof( *pNextJobTime ) );
DPFX(DPFPREP, 8, "Job 0x%p is the next job to fire (at offset time %u).",
pTimedJob, pTimedJob->NextRetryTime.Time32.TimeLow);
}
else
{
//
// Not next job to fire.
//
}
SkipNextRetryTimeComputation:
//
// the following blank line is there to shut up the compiler
//
;
}
DPFX(DPFPREP, 8, "(0x%p) Returning [%i]",
this, fEnumActive);
return fEnumActive;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::SubmitWorkItem - submit a work item for processing and inform
// work thread that another job is available
//
// Entry: Pointer to job information
//
// Exit: Error code
//
// Note: This function assumes that the job data is locked.
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::SubmitWorkItem"
HRESULT CThreadPool::SubmitWorkItem( THREAD_POOL_JOB *const pJobInfo )
{
HRESULT hr;
DPFX(DPFPREP, 8, "(0x%p) Parameters: (0x%p)", this, pJobInfo);
DNASSERT( pJobInfo != NULL );
AssertCriticalSectionIsTakenByThisThread( &m_JobDataLock, TRUE );
DNASSERT( pJobInfo->pCancelFunction != NULL );
//
// initialize
//
hr = DPN_OK;
//
// add job to queue and tell someone that there's a job available
//
m_JobQueue.Lock();
m_JobQueue.EnqueueJob( pJobInfo );
m_JobQueue.Unlock();
#ifdef WINNT
//
// WinNT, submit new I/O completion item
//
DNASSERT( m_hIOCompletionPort != NULL );
if ( PostQueuedCompletionStatus( m_hIOCompletionPort, // completion port
0, // number of bytes written (unused)
IO_COMPLETION_KEY_NEW_JOB, // completion key
NULL // pointer to overlapped structure (unused)
) == FALSE )
{
DWORD dwError;
hr = DPNERR_OUTOFMEMORY;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem posting completion item for new job!" );
DisplayErrorCode( 0, dwError );
goto Failure;
}
#else // WIN95
//
// Win9x, set event that the work thread will listen for
//
DNASSERT( m_JobQueue.GetPendingJobHandle() != NULL );
if ( m_JobQueue.SignalPendingJob() == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
DPFX(DPFPREP, 0, "Failed to signal pending job!" );
goto Failure;
}
#endif
Exit:
if ( hr != DPN_OK )
{
DPFX(DPFPREP, 0, "Problem with SubmitWorkItem!" );
DisplayDNError( 0, hr );
}
DPFX(DPFPREP, 8, "(0x%p) Return [0x%lx]", this, hr);
return hr;
Failure:
goto Exit;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::GetWorkItem - get a work item from the job queue
//
// Entry: Nothing
//
// Exit: Pointer to job information (may be NULL)
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::GetWorkItem"
THREAD_POOL_JOB *CThreadPool::GetWorkItem( void )
{
THREAD_POOL_JOB *pReturn;
//
// initialize
//
pReturn = NULL;
m_JobQueue.Lock();
pReturn = m_JobQueue.DequeueJob();
//
// if we're under Win9x (we have a 'pending job' handle),
// see if the handle needs to be reset
//
if ( m_JobQueue.IsEmpty() != FALSE )
{
DNASSERT( m_JobQueue.GetPendingJobHandle() != NULL );
if ( ResetEvent( m_JobQueue.GetPendingJobHandle() ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem resetting event for pending Win9x jobs!" );
DisplayErrorCode( 0, dwError );
}
}
m_JobQueue.Unlock();
return pReturn;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::SubmitTimerJob - add a timer job to the timer list
//
// Entry: Whether to perform immediately or not
// Retry count
// Boolean indicating that we retry forever
// Retry interval
// Boolean indicating that we wait forever
// Idle wait interval
// Pointer to callback when event fires
// Pointer to callback when event complete
// User context
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::SubmitTimerJob"
HRESULT CThreadPool::SubmitTimerJob( const BOOL fPerformImmediately,
const UINT_PTR uRetryCount,
const BOOL fRetryForever,
const DN_TIME RetryInterval,
const BOOL fIdleWaitForever,
const DN_TIME IdleTimeout,
TIMER_EVENT_CALLBACK *const pTimerCallbackFunction,
TIMER_EVENT_COMPLETE *const pTimerCompleteFunction,
void *const pContext )
{
HRESULT hr;
TIMER_OPERATION_ENTRY *pEntry;
THREAD_POOL_JOB *pJob;
BOOL fTimerJobListLocked;
DNASSERT( uRetryCount != 0 );
DNASSERT( pTimerCallbackFunction != NULL );
DNASSERT( pTimerCompleteFunction != NULL );
DNASSERT( pContext != NULL ); // must be non-NULL because it's the lookup key to remove job
//
// initialize
//
hr = DPN_OK;
pEntry = NULL;
pJob = NULL;
fTimerJobListLocked = FALSE;
LockJobData();
//
// allocate new enum entry
//
pEntry = static_cast<TIMER_OPERATION_ENTRY*>( m_TimerEntryPool.Get( &m_TimerEntryPool ) );
if ( pEntry == NULL )
{
hr = DPNERR_OUTOFMEMORY;
DPFX(DPFPREP, 0, "Cannot allocate memory to add to timer list!" );
goto Failure;
}
DNASSERT( pEntry->pContext == NULL );
DPFX(DPFPREP, 8, "Created timer entry 0x%p (context 0x%p, immed. %i, tries %u, forever %i, interval %u, timeout %u, forever = %i).",
pEntry, pContext, fPerformImmediately, uRetryCount, fRetryForever, RetryInterval.Time32.TimeLow, IdleTimeout.Time32.TimeLow, fIdleWaitForever);
//
// build timer entry block
//
pEntry->pContext = pContext;
pEntry->fPerformImmediately = fPerformImmediately;
pEntry->uRetryCount = uRetryCount;
pEntry->fRetryForever = fRetryForever;
pEntry->RetryInterval = RetryInterval;
pEntry->IdleTimeout = IdleTimeout;
pEntry->fIdleWaitForever = fIdleWaitForever;
pEntry->pTimerCallback = pTimerCallbackFunction;
pEntry->pTimerComplete = pTimerCompleteFunction;
//
// set this enum to fire as soon as it gets a chance
//
memset( &pEntry->NextRetryTime, 0x00, sizeof( pEntry->NextRetryTime ) );
pJob = static_cast<THREAD_POOL_JOB*>( m_JobPool.Get( &m_JobPool ) );
if ( pJob == NULL )
{
hr = DPNERR_OUTOFMEMORY;
DPFX(DPFPREP, 0, "Cannot allocate memory for enum job!" );
goto Failure;
}
//
// Create job for work thread.
//
pJob->pCancelFunction = CancelRefreshTimerJobs;
pJob->JobType = JOB_REFRESH_TIMER_JOBS;
// set our dummy parameter to simulate passing data
DEBUG_ONLY( pJob->JobData.JobRefreshTimedJobs.uDummy = 0 );
LockTimerData();
fTimerJobListLocked = TRUE;
//
// we can submit the 'ENUM_REFRESH' job before inserting the enum entry
// into the active enum list because nobody will be able to pull the
// 'ENUM_REFRESH' job from the queue since we have the queue locked
//
hr = SubmitWorkItem( pJob );
if ( hr != DPN_OK )
{
DPFX(DPFPREP, 0, "Problem submitting enum work item" );
DisplayDNError( 0, hr );
goto Failure;
}
//
// debug block to check for duplicate contexts
//
DEBUG_ONLY(
{
CBilink *pTempLink;
pTempLink = m_TimerJobList.GetNext();
while ( pTempLink != &m_TimerJobList )
{
TIMER_OPERATION_ENTRY *pTempTimerEntry;
pTempTimerEntry = TIMER_OPERATION_ENTRY::TimerOperationFromLinkage( pTempLink );
DNASSERT( pTempTimerEntry->pContext != pContext );
pTempLink = pTempLink->GetNext();
}
}
);
//
// link to rest of list
//
pEntry->Linkage.InsertAfter( &m_TimerJobList );
UnlockTimerData();
fTimerJobListLocked = FALSE;
//
// If we're on NT, attempt to start the special thread, or kick it
// if it's already running.
//
#ifdef WINNT
hr = StartNTTimerThread();
if ( hr != DPN_OK )
{
DPFX(DPFPREP, 0, "Cannot spin up NT timer thread!" );
DisplayDNError( 0, hr );
goto Failure;
}
#endif
Exit:
UnlockJobData();
if ( hr != DPN_OK )
{
DPFX(DPFPREP, 0, "Problem with SubmitEnumJob" );
DisplayDNError( 0, hr );
}
return hr;
Failure:
if ( pEntry != NULL )
{
m_TimerEntryPool.Release( &m_TimerEntryPool, pEntry );
DEBUG_ONLY( pEntry = NULL );
}
if ( pJob != NULL )
{
m_JobPool.Release( &m_JobPool, pJob );
DEBUG_ONLY( pJob = NULL );
}
//
// It's possible that the enum thread has been started for this enum.
// Since there's no way to stop it without completing the enums or
// closing the SP, leave it running.
//
if ( fTimerJobListLocked != FALSE )
{
UnlockTimerData();
fTimerJobListLocked = FALSE;
}
goto Exit;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::CancelRefreshTimerJobs - cancel job to refresh timer jobs
//
// Entry: Job data
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::CancelRefreshTimerJobs"
void CThreadPool::CancelRefreshTimerJobs( THREAD_POOL_JOB *const pJob )
{
DNASSERT( pJob != NULL );
//
// this function doesn't need to do anything
//
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::StopTimerJob - remove timer job from list
//
// Entry: Pointer to job context (these MUST be unique for jobs)
// Command result
//
// Exit: Boolean indicating whether a job was stopped or not
//
// Note: This function is for the forced removal of a job from the timed job
// list. It is assumed that the caller of this function will clean
// up any messes.
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::StopTimerJob"
BOOL CThreadPool::StopTimerJob( void *const pContext, const HRESULT hCommandResult )
{
BOOL fComplete = FALSE;
CBilink * pTempEntry;
TIMER_OPERATION_ENTRY * pTimerEntry;
DNASSERT( pContext != NULL );
DPFX(DPFPREP, 8, "Parameters (0x%p, 0x%lx)", pContext, hCommandResult);
//
// initialize
//
LockTimerData();
pTempEntry = m_TimerJobList.GetNext();
while ( pTempEntry != &m_TimerJobList )
{
pTimerEntry = TIMER_OPERATION_ENTRY::TimerOperationFromLinkage( pTempEntry );
if ( pTimerEntry->pContext == pContext )
{
//
// remove this link from the list
//
pTimerEntry->Linkage.RemoveFromList();
fComplete = TRUE;
//
// terminate loop
//
break;
}
pTempEntry = pTempEntry->GetNext();
}
UnlockTimerData();
//
// tell owner that the job is complete and return the job to the pool
// outside of the lock
//
if (fComplete)
{
DPFX(DPFPREP, 8, "Found timer entry 0x%p (context 0x%p), completing with result 0x%lx.",
pTimerEntry, pTimerEntry->pContext, hCommandResult);
pTimerEntry->pTimerComplete( hCommandResult, pTimerEntry->pContext );
//
// Relock the timer list so we can safely put items back in the pool,
//
LockTimerData();
m_TimerEntryPool.Release( &m_TimerEntryPool, pTimerEntry );
UnlockTimerData();
}
DPFX(DPFPREP, 8, "Returning [%i]", fComplete);
return fComplete;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::ModifyTimerJobNextRetryTime - update a timer job's next retry time
//
// Entry: Pointer to job context (these MUST be unique for jobs)
// New time for next retry
//
// Exit: Boolean indicating whether a job was found & updated or not
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::ModifyTimerJobNextRetryTime"
BOOL CThreadPool::ModifyTimerJobNextRetryTime( void *const pContext,
DN_TIME * const pNextRetryTime)
{
BOOL fFound;
CBilink * pTempEntry;
TIMER_OPERATION_ENTRY * pTimerEntry;
INT_PTR iResult;
DNASSERT( pContext != NULL );
DPFX(DPFPREP, 8, "Parameters (0x%p, 0x%p)", pContext, pNextRetryTime);
//
// initialize
//
fFound = FALSE;
LockJobData();
LockTimerData();
pTempEntry = m_TimerJobList.GetNext();
while ( pTempEntry != &m_TimerJobList )
{
pTimerEntry = TIMER_OPERATION_ENTRY::TimerOperationFromLinkage( pTempEntry );
if ( pTimerEntry->pContext == pContext )
{
iResult = DNTimeCompare(&(pTimerEntry->NextRetryTime), pNextRetryTime);
if (iResult != 0)
{
THREAD_POOL_JOB * pJob;
HRESULT hr;
DN_TIME NextRetryTimeDifference;
DN_TIME NewRetryInterval;
if (iResult < 0)
{
//
// Next time to fire is now later.
//
DNTimeSubtract(pNextRetryTime, &(pTimerEntry->NextRetryTime), &NextRetryTimeDifference);
DNTimeAdd(&(pTimerEntry->RetryInterval), &NextRetryTimeDifference, &NewRetryInterval);
DPFX(DPFPREP, 7, "Timer 0x%p next retry time delayed by %u ms from offset %u to offset %u, modifying interval from %u to %u.",
pTimerEntry,
NextRetryTimeDifference.Time32.TimeLow,
pTimerEntry->NextRetryTime.Time32.TimeLow,
pNextRetryTime->Time32.TimeLow,
pTimerEntry->RetryInterval.Time32.TimeLow,
NewRetryInterval.Time32.TimeLow);
}
else
{
//
// Next time to fire is now earlier.
//
DNTimeSubtract(&(pTimerEntry->NextRetryTime), pNextRetryTime, &NextRetryTimeDifference);
DNTimeSubtract(&(pTimerEntry->RetryInterval), &NextRetryTimeDifference, &NewRetryInterval);
DPFX(DPFPREP, 7, "Timer 0x%p next retry time moved up by %u ms from offset %u to offset %u, modifying interval from %u to %u.",
pTimerEntry,
NextRetryTimeDifference.Time32.TimeLow,
pTimerEntry->NextRetryTime.Time32.TimeLow,
pNextRetryTime->Time32.TimeLow,
pTimerEntry->RetryInterval.Time32.TimeLow,
NewRetryInterval.Time32.TimeLow);
}
memcpy(&(pTimerEntry->RetryInterval), &NewRetryInterval, sizeof(DN_TIME));
memcpy(&(pTimerEntry->NextRetryTime), pNextRetryTime, sizeof(DN_TIME));
//
// Submit a refresh job so the threads will notice the new time.
//
pJob = static_cast<THREAD_POOL_JOB*>( m_JobPool.Get( &m_JobPool ) );
if ( pJob == NULL )
{
DPFX(DPFPREP, 0, "Cannot allocate memory for enum refresh job!" );
}
else
{
pJob->pCancelFunction = CancelRefreshTimerJobs;
pJob->JobType = JOB_REFRESH_TIMER_JOBS;
// set our dummy parameter to simulate passing data
DEBUG_ONLY( pJob->JobData.JobRefreshTimedJobs.uDummy = 0 );
hr = SubmitWorkItem( pJob );
if ( hr != DPN_OK )
{
DPFX(DPFPREP, 0, "Problem submitting enum work item" );
DisplayDNError( 0, hr );
m_JobPool.Release( &m_JobPool, pJob );
DEBUG_ONLY( pJob = NULL );
}
}
}
else
{
//
// The intervals are the same, no change necessary.
//
DPFX(DPFPREP, 7, "Timer 0x%p next retry time was unchanged (offset %u), not changing interval from %u.",
pTimerEntry,
pTimerEntry->NextRetryTime.Time32.TimeLow,
pTimerEntry->RetryInterval.Time32.TimeLow);
}
fFound = TRUE;
//
// Terminate loop
//
break;
}
pTempEntry = pTempEntry->GetNext();
}
UnlockTimerData();
UnlockJobData();
DPFX(DPFPREP, 8, "Returning [%i]", fFound);
return fFound;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::SpawnDialogThread - start a secondary thread to display service
// provider UI.
//
// Entry: Pointer to dialog function
// Dialog context
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::SpawnDialogThread"
HRESULT CThreadPool::SpawnDialogThread( DIALOG_FUNCTION *const pDialogFunction, void *const pDialogContext )
{
HRESULT hr;
HANDLE hDialogThread;
DIALOG_THREAD_PARAM *pThreadParam;
DWORD dwThreadID;
DNASSERT( pDialogFunction != NULL );
DNASSERT( pDialogContext != NULL ); // why would anyone not want a dialog context??
//
// initialize
//
hr = DPN_OK;
pThreadParam = NULL;
//
// create and initialize thread param
//
pThreadParam = static_cast<DIALOG_THREAD_PARAM*>( DNMalloc( sizeof( *pThreadParam ) ) );
if ( pThreadParam == NULL )
{
hr = DPNERR_OUTOFMEMORY;
DPFX(DPFPREP, 0, "Failed to allocate memory for dialog thread!" );
goto Failure;
}
pThreadParam->pDialogFunction = pDialogFunction;
pThreadParam->pContext = pDialogContext;
pThreadParam->pThisThreadPool = this;
//
// assume that a thread will be created
//
IncrementActiveThreadCount();
//
// create thread
//
hDialogThread = CreateThread( NULL, // pointer to security (none)
0, // stack size (default)
DialogThreadProc, // thread procedure
pThreadParam, // thread param
0, // creation flags (none)
&dwThreadID ); // pointer to thread ID
if ( hDialogThread == NULL )
{
DWORD dwError;
//
// decrement active thread count and report error
//
DecrementActiveThreadCount();
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to start dialog thread!" );
DisplayErrorCode( 0, dwError );
goto Failure;
}
if ( CloseHandle( hDialogThread ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem closing handle from create dialog thread!" );
DisplayErrorCode( 0, dwError );
}
Exit:
return hr;
Failure:
if ( pThreadParam != NULL )
{
DNFree( pThreadParam );
pThreadParam = NULL;
}
goto Exit;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::GetIOThreadCount - get I/O thread count
//
// Entry: Pointer to variable to fill
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::GetIOThreadCount"
HRESULT CThreadPool::GetIOThreadCount( LONG *const piThreadCount )
{
HRESULT hr;
DNASSERT( piThreadCount != NULL );
//
// initialize
//
hr = DPN_OK;
Lock();
if ( IsThreadCountReductionAllowed() )
{
*piThreadCount = GetIntendedThreadCount();
}
else
{
#ifdef WIN95
*piThreadCount = ThreadCount();
#else // WINNT
DNASSERT( NTCompletionThreadCount() != 0 );
*piThreadCount = NTCompletionThreadCount();
#endif
}
Unlock();
return hr;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::SetIOThreadCount - set I/O thread count
//
// Entry: New thread count
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::SetIOThreadCount"
HRESULT CThreadPool::SetIOThreadCount( const LONG iThreadCount )
{
HRESULT hr;
DNASSERT( iThreadCount > 0 );
//
// initialize
//
hr = DPN_OK;
Lock();
if ( IsThreadCountReductionAllowed() )
{
DPFX(DPFPREP, 4, "Thread pool not locked down, setting intended thread count to %i.", iThreadCount );
SetIntendedThreadCount( iThreadCount );
}
else
{
#ifdef WIN95
//
// Win9x can only increase threads once locked down.
//
if ( iThreadCount > ThreadCount() )
{
INT_PTR iDeltaThreads;
iDeltaThreads = iThreadCount - ThreadCount();
DPFX(DPFPREP, 4, "Thread pool locked down, spawning %i new 9x threads (for a total of %i).",
iDeltaThreads, iThreadCount );
while ( iDeltaThreads > 0 )
{
iDeltaThreads--;
StartSecondaryWin9xIOThread();
}
}
else
{
DPFX(DPFPREP, 4, "Thread pool locked down and already has %i 9x threads, not adjusting to %i.",
ThreadCount(), iThreadCount );
}
#else // WINNT
//
// WinNT can have many threads. If the user wants more threads, attempt
// to boost the thread pool to the requested amount (if we fail to
// start a new thread, too bad). If the user wants fewer threads, check
// to see if the thread pool has been locked out of changes. If not,
// start killing off the threads.
//
if ( iThreadCount > NTCompletionThreadCount() )
{
INT_PTR iDeltaThreads;
iDeltaThreads = iThreadCount - NTCompletionThreadCount();
DPFX(DPFPREP, 4, "Thread pool locked down, spawning %i new NT threads (for a total of %i).",
iDeltaThreads, iThreadCount );
while ( iDeltaThreads > 0 )
{
iDeltaThreads--;
StartNTCompletionThread();
}
}
else
{
DPFX(DPFPREP, 4, "Thread pool locked down and already has %i NT threads, not adjusting to %i.",
NTCompletionThreadCount(), iThreadCount );
}
#endif
}
Unlock();
return hr;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::PreventThreadPoolReduction - prevents the thread pool size from being reduced
//
// Entry: None
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::PreventThreadPoolReduction"
HRESULT CThreadPool::PreventThreadPoolReduction( void )
{
HRESULT hr = DPN_OK;
LONG iDesiredThreads;
DWORD dwTemp;
DPNHCAPS dpnhcaps;
DN_TIME NATHelpRetryTime;
DN_TIME NATHelpTimeoutTime;
#ifdef DEBUG
DWORD dwStartTime;
DWORD dwCurrentTime;
#endif // DEBUG
Lock();
//
// If we haven't already clamped down, do so, and spin up the threads.
//
if ( IsThreadCountReductionAllowed() )
{
m_fAllowThreadCountReduction = FALSE;
DNASSERT( GetIntendedThreadCount() > 0 );
DNASSERT( ThreadCount() == 0 );
iDesiredThreads = GetIntendedThreadCount();
SetIntendedThreadCount( 0 );
DPFX(DPFPREP, 3, "Locking down thread count at %i.", iDesiredThreads );
#ifdef DEBUG
dwStartTime = GETTIMESTAMP();
#endif // DEBUG
//
// OS-specific thread starting.
//
#ifdef WINNT
//
// WinNT
//
DNASSERT( NTCompletionThreadCount() == 0 );
while ( iDesiredThreads > 0 )
{
iDesiredThreads--;
StartNTCompletionThread();
}
//
// If at least one thread was created, the SP will perform in a
// non-optimal fashion, but we will still function. If no threads
// were created, fail.
//
if ( ThreadCount() == 0 )
{
hr = DPNERR_OUTOFMEMORY;
DPFX(DPFPREP, 0, "Unable to create any threads to service NT I/O completion port!" );
goto Failure;
}
#else // WIN95
//
// Windows 9x
//
//
// Spin up the 9x threads. Start with the primary thread.
//
StartPrimaryWin9xIOThread();
if ( ThreadCount() == 0 )
{
DPFX(DPFPREP, 0, "Failed to start primary Win9x I/O thread!" );
goto Failure;
}
iDesiredThreads--;
//
// Spin up the requested number of secondary threads.
//
while ( iDesiredThreads > 0 )
{
iDesiredThreads--;
StartSecondaryWin9xIOThread();
}
#endif
#ifdef DEBUG
dwCurrentTime = GETTIMESTAMP();
DPFX(DPFPREP, 8, "Spent %u ms starting %i threads.", (dwCurrentTime - dwStartTime), ThreadCount());
#endif // DEBUG
if (IsNATHelpLoaded())
{
#ifdef DEBUG
dwStartTime = dwCurrentTime;
#endif // DEBUG
//
// Initialize the timer values.
//
NATHelpRetryTime.Time32.TimeHigh = 0;
NATHelpRetryTime.Time32.TimeLow = -1;
NATHelpTimeoutTime.Time32.TimeHigh = 0;
NATHelpTimeoutTime.Time32.TimeLow = 0;
//
// Loop through each NAT help object.
//
for(dwTemp = 0; dwTemp < MAX_NUM_DIRECTPLAYNATHELPERS; dwTemp++)
{
if (g_papNATHelpObjects[dwTemp] != NULL)
{
if (g_fUseNATHelpAlert)
{
//
// Install the NAT Help alert mechanism.
//
#ifdef WINNT
hr = IDirectPlayNATHelp_SetAlertIOCompletionPort(g_papNATHelpObjects[dwTemp],
m_hIOCompletionPort,
IO_COMPLETION_KEY_NATHELP_UPDATE,
0, // doesn't matter how many concurrent threads
0);
if (hr != DPNH_OK)
{
DPFX(DPFPREP, 0, "Couldn't set NAT Help alert I/O completion port for object %u (error = 0x%lx), continuing.",
dwTemp, hr);
//
// It's not fatal, we still have some level of functionality.
//
}
#else // WIN95
DNASSERT(m_hNATHelpUpdateEvent != NULL);
hr = IDirectPlayNATHelp_SetAlertEvent(g_papNATHelpObjects[dwTemp],
m_hNATHelpUpdateEvent,
0);
if (hr != DPNH_OK)
{
DPFX(DPFPREP, 0, "Couldn't set NAT Help alert event for object %u (error = 0x%lx), continuing.",
dwTemp, hr);
//
// It's not fatal, we still have some level of functionality.
//
}
#endif
}
else
{
DPFX(DPFPREP, 1, "Not installing NAT Help alert event/IO completion port.");
}
//
// Determine how often to refresh the NAT help caps in the future.
//
// We're going to force server detection now. This will increase the time
// it takes to startup up this Enum/Connect/Listen operation, but is
// necessary since the IDirectPlayNATHelp::GetRegisteredAddresses call in
// CSocketPort::BindToInternetGateway and possibly the
// IDirectPlayNATHelp::QueryAddress call in CSocketPort::MungePublicAddress
// could occur before the first NATHelp GetCaps timer fires.
// In the vast majority of NAT cases, the gateway is already available.
// If we hadn't detected that yet (because we hadn't called
// IDirectPlayNATHelp::GetCaps with DPNHGETCAPS_UPDATESERVERSTATUS)
// then we would get an incorrect result from GetRegisteredAddresses or
// QueryAddress.
//
ZeroMemory(&dpnhcaps, sizeof(dpnhcaps));
dpnhcaps.dwSize = sizeof(dpnhcaps);
hr = IDirectPlayNATHelp_GetCaps(g_papNATHelpObjects[dwTemp],
&dpnhcaps,
DPNHGETCAPS_UPDATESERVERSTATUS);
if (FAILED(hr))
{
DPFX(DPFPREP, 0, "Failed getting NAT Help capabilities (error = 0x%lx), continuing.",
hr);
//
// NAT Help will probably not work correctly, but that won't prevent
// local connections from working. Consider it non-fatal.
//
hr = DPN_OK;
}
else
{
//
// See if this is the shortest interval.
//
if (dpnhcaps.dwRecommendedGetCapsInterval < NATHelpRetryTime.Time32.TimeLow)
{
NATHelpRetryTime.Time32.TimeLow = dpnhcaps.dwRecommendedGetCapsInterval;
}
}
}
else
{
//
// No object loaded in this slot.
//
}
}
//
// If there's a retry interval, submit a timer job.
//
if (NATHelpRetryTime.Time32.TimeLow != -1)
{
//
// Attempt to add timer job that will refresh the lease and server
// status.
// Although we're submitting it as a periodic timer, it's actually
// not going to be called at regular intervals.
// There is a race condition where the alert event/IOCP may have
// been fired already, and another thread tried to cancel this timer
// which hasn't been submitted yet. The logic to handle this race
// is placed there (HandleNATHelpUpdate); here we can assume we
// are the first person to submit the refresh timer.
//
DPFX(DPFPREP, 7, "Submitting NAT Help refresh timer (for every %u ms) for thread pool 0x%p.",
NATHelpRetryTime.Time32.TimeLow, this);
DNASSERT(! this->m_fNATHelpTimerJobSubmitted );
this->m_fNATHelpTimerJobSubmitted = TRUE;
hr = SubmitTimerJob(FALSE, // don't perform immediately
1, // retry count
TRUE, // retry forever
NATHelpRetryTime, // retry timeout
TRUE, // wait forever
NATHelpTimeoutTime, // idle timeout
CThreadPool::NATHelpTimerFunction, // periodic callback function
CThreadPool::NATHelpTimerComplete, // completion function
this); // context
if (hr != DPN_OK)
{
m_fNATHelpTimerJobSubmitted = FALSE;
DPFX(DPFPREP, 0, "Failed to submit timer job to watch over NAT Help!" );
//
// NAT Help will probably not work correctly, but that won't
// prevent local connections from working. Consider it
// non-fatal.
//
}
}
#ifdef DEBUG
dwCurrentTime = GETTIMESTAMP();
DPFX(DPFPREP, 8, "Spent %u ms preparing DPNATHLP.", (dwCurrentTime - dwStartTime));
#endif // DEBUG
}
}
else
{
DPFX(DPFPREP, 3, "Thread count already locked down (at %i).", ThreadCount() );
}
Exit:
Unlock();
return hr;
Failure:
goto Exit;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::NATHelpTimerComplete - NAT Help timer job has completed
//
// Entry: Timer result code
// Context
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::NATHelpTimerComplete"
void CThreadPool::NATHelpTimerComplete( const HRESULT hResult, void * const pContext )
{
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::NATHelpTimerFunction - NAT Help timer job needs service
//
// Entry: Pointer to context
// Pointer to current timer retry interval
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::NATHelpTimerFunction"
void CThreadPool::NATHelpTimerFunction( void * const pContext, DN_TIME * const pRetryInterval )
{
CThreadPool * pThisThreadPool;
DWORD dwError;
DNASSERT( pContext != NULL );
pThisThreadPool = (CThreadPool*) pContext;
//
// The NAT Help GetCaps function may block for a non-trivial amount of time.
// It would really best if we didn't perform it in the timer thread, and thus hold
// up all the other timers. There's only 1 timer thread, but we can survive
// without 1 I/O thread for a little while.
// We could submit a delayed job to update, but that's a little bit of over-
// engineering in my opinion. We can already have an alert mechanism in
// place, so let's just ping that and let our I/O threads do their job.
// Don't request the update if a thread is already handling the update, though.
//
pThisThreadPool->Lock();
if (pThisThreadPool->m_dwNATHelpUpdateThreadID == 0)
{
#ifdef DEBUG
//
// Reset the not-scheduled counter since we're now scheduling an update.
//
if (pThisThreadPool->m_dwNumNATHelpUpdatesNotScheduled > 0)
{
DPFX(DPFPREP, 1, "Thread pool 0x%p scheduling NAT Help update after %u attempts.",
pThisThreadPool, pThisThreadPool->m_dwNumNATHelpUpdatesNotScheduled);
pThisThreadPool->m_dwNumNATHelpUpdatesNotScheduled = 0;
}
else
{
DPFX(DPFPREP, 9, "Thread pool 0x%p scheduling NAT Help update.", pThisThreadPool);
}
#endif // DEBUG
pThisThreadPool->Unlock();
#ifdef WINNT
//
// Submit an I/O completion packet.
//
if (! PostQueuedCompletionStatus(pThisThreadPool->GetIOCompletionPort(), // handle of completion port
0, // number of bytes transferred
IO_COMPLETION_KEY_NATHELP_UPDATE, // completion key
NULL)) // pointer to overlapped structure (none)
{
dwError = GetLastError();
DPFX(DPFPREP, 0, "Couldn't submit NAT Help update I/O complet packet (err = %u)!", dwError );
DisplayErrorCode(0, dwError);
}
#else // ! WINNT
//
// WinSock 1 doesn't support the alert event, so we have no choice but to
// handle it here.
//
if (GetWinsockVersion() == 1)
{
pThisThreadPool->HandleNATHelpUpdate(pRetryInterval);
}
else
{
//
// This is a huge hack, but kick the receive thread, since it appears
// that occasionally we're not getting the event set when the I/O
// completes. We do it in the NAT timer simply because it's in
// existence.
// Setting the event when there's nothing to receive is harmless.
//
if (! SetEvent(pThisThreadPool->GetWinsock2ReceiveCompleteEvent()))
{
dwError = GetLastError();
DPFX(DPFPREP, 0, "Couldn't set WinSock 2 receive event (err = %u)!", dwError);
DisplayErrorCode(0, dwError);
}
//
// Note that we also don't submit a delayed job because we don't want
// to eat up our only job processing thread (the primary Win9x thread)
// with the lengthy update process. Setting the event ensures only the
// secondary thread to pick it up.
//
if (! SetEvent(pThisThreadPool->GetNATHelpUpdateEvent()))
{
dwError = GetLastError();
DPFX(DPFPREP, 0, "Couldn't set NAT Help update event (err = %u)!", dwError);
DisplayErrorCode(0, dwError);
}
}
#endif // ! WINNT
}
else
{
#ifdef DEBUG
pThisThreadPool->m_dwNumNATHelpUpdatesNotScheduled++;
DPFX(DPFPREP, 1, "Thread %u/0x%x already handling NAT Help update, not requesting another update (thread pool = 0x%p, timer = 0x%p, not-scheduled count = %u).",
pThisThreadPool->m_dwNATHelpUpdateThreadID,
pThisThreadPool->m_dwNATHelpUpdateThreadID,
pThisThreadPool,
pRetryInterval,
pThisThreadPool->m_dwNumNATHelpUpdatesNotScheduled);
//
// This count shouldn't get very large. We're usually only refreshing
// every 25 seconds or thereabouts, so even in a 12 hour stress run
// where the GetCaps call blocked for the entire duration, we should
// see fewer than 2000 timer expirations (~1728). We'll use 500, or
// over 3 hours of 25 second intervals. If this assert fires, it means
// something is wrong with the thread pool or NAT Help.
//
DNASSERT(pThisThreadPool->m_dwNumNATHelpUpdatesNotScheduled < 500);
#endif // DEBUG
//
// Increase the interval to a really long time so that we don't attempt
// to fire this timer again (on our own). The thread that is currently
// refreshing NAT Help will set this back to something appropriate when
// it finally comes back to the land of the living.
//
pRetryInterval->Time32.TimeLow = INFINITE;
pThisThreadPool->Unlock();
}
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::SubmitDelayedCommand - submit request to enum query to remote session
//
// Entry: Pointer to callback function
// Pointer to cancel function
// Pointer to callback context
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::SubmitDelayedCommand"
HRESULT CThreadPool::SubmitDelayedCommand( JOB_FUNCTION *const pFunction, JOB_FUNCTION *const pCancelFunction, void *const pContext )
{
HRESULT hr;
THREAD_POOL_JOB *pJob;
BOOL fJobDataLocked;
DPFX(DPFPREP, 6, "Parameters (0x%p, 0x%p, 0x%p)", pFunction, pCancelFunction, pContext);
DNASSERT( pFunction != NULL );
DNASSERT( pCancelFunction != NULL );
//
// initialize
//
hr = DPN_OK;
pJob = NULL;
fJobDataLocked = FALSE;
pJob = static_cast<THREAD_POOL_JOB*>( m_JobPool.Get( &m_JobPool ) );
if ( pJob == NULL )
{
hr = DPNERR_OUTOFMEMORY;
DPFX(DPFPREP, 0, "Cannot allocate job for DelayedCommand!" );
goto Failure;
}
pJob->JobType = JOB_DELAYED_COMMAND;
pJob->pCancelFunction = pCancelFunction;
pJob->JobData.JobDelayedCommand.pCommandFunction = pFunction;
pJob->JobData.JobDelayedCommand.pContext = pContext;
LockJobData();
fJobDataLocked = TRUE;
hr = SubmitWorkItem( pJob );
if ( hr != DPN_OK )
{
DPFX(DPFPREP, 0, "Problem submitting DelayedCommand job!" );
DisplayDNError( 0, hr );
goto Failure;
}
Exit:
if ( fJobDataLocked != FALSE )
{
UnlockJobData();
fJobDataLocked = FALSE;
}
DPFX(DPFPREP, 6, "Returning: [0x%lx]", hr);
return hr;
Failure:
if ( pJob != NULL )
{
m_JobPool.Release( &m_JobPool, pJob );
}
goto Exit;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::AddSocketPort - add a socket to the Win9x watch list
//
// Entry: Pointer to SocketPort
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::AddSocketPort"
HRESULT CThreadPool::AddSocketPort( CSocketPort *const pSocketPort )
{
HRESULT hr;
BOOL fSocketAdded;
DNASSERT( pSocketPort != NULL );
//
// initialize
//
hr = DPN_OK;
fSocketAdded = FALSE;
Lock();
//
// We're capped by the number of sockets we can use for Winsock1. Make
// sure we don't allocate too many sockets.
//
if ( m_uReservedSocketCount == FD_SETSIZE )
{
hr = DPNERR_OUTOFMEMORY;
DPFX(DPFPREP, 0, "There are too many sockets allocated on Winsock1!" );
goto Failure;
}
m_uReservedSocketCount++;
DNASSERT( m_SocketSet.fd_count < FD_SETSIZE );
m_pSocketPorts[ m_SocketSet.fd_count ] = pSocketPort;
m_SocketSet.fd_array[ m_SocketSet.fd_count ] = pSocketPort->GetSocket();
m_SocketSet.fd_count++;
fSocketAdded = TRUE;
//
// add a reference to note that this socket port is being used by the thread
// pool
//
pSocketPort->AddRef();
if ( m_JobQueue.SignalPendingJob() == FALSE )
{
DPFX(DPFPREP, 0, "Failed to signal pending job when adding socket port to active list!" );
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
Exit:
Unlock();
return hr;
Failure:
if ( fSocketAdded != FALSE )
{
AssertCriticalSectionIsTakenByThisThread( &m_Lock, TRUE );
m_SocketSet.fd_count--;
m_pSocketPorts[ m_SocketSet.fd_count ] = NULL;
m_SocketSet.fd_array[ m_SocketSet.fd_count ] = NULL;
fSocketAdded = FALSE;
}
m_uReservedSocketCount--;
goto Exit;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::RemoveSocketPort - remove a socket from the Win9x watch list
//
// Entry: Pointer to socket port to remove
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::RemoveSocketPort"
void CThreadPool::RemoveSocketPort( CSocketPort *const pSocketPort )
{
UINT_PTR uIndex;
DNASSERT( pSocketPort != NULL );
Lock();
uIndex = m_SocketSet.fd_count;
DNASSERT( uIndex != 0 );
while ( uIndex != 0 )
{
uIndex--;
if ( m_pSocketPorts[ uIndex ] == pSocketPort )
{
m_uReservedSocketCount--;
m_SocketSet.fd_count--;
memmove( &m_pSocketPorts[ uIndex ],
&m_pSocketPorts[ uIndex + 1 ],
( sizeof( m_pSocketPorts[ uIndex ] ) * ( m_SocketSet.fd_count - uIndex ) ) );
memmove( &m_SocketSet.fd_array[ uIndex ],
&m_SocketSet.fd_array[ uIndex + 1 ],
( sizeof( m_SocketSet.fd_array[ uIndex ] ) * ( m_SocketSet.fd_count - uIndex ) ) );
//
// clear last entry which is now unused
//
memset( &m_pSocketPorts[ m_SocketSet.fd_count ], 0x00, sizeof( m_pSocketPorts[ m_SocketSet.fd_count ] ) );
memset( &m_SocketSet.fd_array[ m_SocketSet.fd_count ], 0x00, sizeof( m_SocketSet.fd_array[ m_SocketSet.fd_count ] ) );
//
// end the loop
//
uIndex = 0;
}
}
Unlock();
pSocketPort->DecRef();
//
// It's really not necessary to signal a new job here because there were
// active sockets on the last iteration of the Win9x thread. That means the
// Win9x thread was in a polling mode to check for sockets and the next time
// through it will notice that there is a missing socket. By signalling the
// job event we reduce the time needed for the thread to figure out that the
// socket is gone.
//
if ( m_JobQueue.SignalPendingJob() == FALSE )
{
DPFX(DPFPREP, 0, "Failed to signal pending job when removeing socket port to active list!" );
}
}
//**********************************************************************
#ifdef WINNT
//**********************************************************************
// ------------------------------
// CThreadPool::StartNTTimerThread - start the timer thread for NT
//
// Entry: Nothing
//
// Exit: Error code
//
// Note: This function assumes that the enum data is locked.
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::StartNTTimerThread"
HRESULT CThreadPool::StartNTTimerThread( void )
{
HRESULT hr;
HANDLE hThread;
DWORD dwThreadID;
//
// initialize
//
hr = DPN_OK;
DNASSERT( m_JobQueue.GetPendingJobHandle() != NULL );
if ( m_fNTTimerThreadRunning != FALSE )
{
//
// the enum thread is already running, poke it to note new enums
//
if ( SetEvent( m_JobQueue.GetPendingJobHandle() ) == FALSE )
{
DWORD dwError;
hr = DPNERR_OUTOFMEMORY;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem setting event to wake NTTimerThread!" );
DisplayErrorCode( 0, dwError );
goto Failure;
}
goto Exit;
}
IncrementActiveThreadCount();
hThread = CreateThread( NULL, // pointer to security attributes (none)
0, // stack size (default)
WinNTTimerThread, // thread function
this, // thread parameter
0, // creation flags (none, start running now)
&dwThreadID // pointer to thread ID
);
if ( hThread == NULL )
{
DWORD dwError;
hr = DPNERR_OUTOFMEMORY;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to create NT timer thread!" );
DisplayErrorCode( 0, dwError );
DecrementActiveThreadCount();
goto Failure;
}
//
// note that the thread is running and close the handle to the thread
//
m_fNTTimerThreadRunning = TRUE;
DPFX(DPFPREP, 8, "Creating NT-Timer thread: 0x%x\tTotal Thread Count: %d\tNT Completion Thread Count: %d", dwThreadID, ThreadCount(), NTCompletionThreadCount() );
if ( CloseHandle( hThread ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem closing handle after starting NTTimerThread!" );
DisplayErrorCode( 0, dwError );
}
Exit:
return hr;
Failure:
goto Exit;
}
//**********************************************************************
#endif // WINNT
#ifdef WINNT
//**********************************************************************
// ------------------------------
// CThreadPool::WakeNTTimerThread - wake the timer thread because a timed event
// has been added
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::WakeNTTimerThread"
void CThreadPool::WakeNTTimerThread( void )
{
LockJobData();
DNASSERT( m_JobQueue.GetPendingJobHandle() != FALSE );
if ( SetEvent( m_JobQueue.GetPendingJobHandle() ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem setting event to wake up NT timer thread!" );
DisplayErrorCode( 0, dwError );
}
UnlockJobData();
}
//**********************************************************************
#endif // WINNT
//**********************************************************************
// ------------------------------
// CThreadPool::RemoveTimerOperationEntry - remove timer operation job from list
//
// Entry: Pointer to timer operation
// Result code to return
//
// Exit: Nothing
//
// Note: This function assumes that the list is appropriately locked
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::RemoveTimerOperationEntry"
void CThreadPool::RemoveTimerOperationEntry( TIMER_OPERATION_ENTRY *const pTimerEntry, const HRESULT hJobResult )
{
DNASSERT( pTimerEntry != NULL );
AssertCriticalSectionIsTakenByThisThread( &m_TimerDataLock, TRUE );
//
// remove this link from the list, tell owner that the job is complete and
// return the job to the pool
//
pTimerEntry->Linkage.RemoveFromList();
pTimerEntry->pTimerComplete( hJobResult, pTimerEntry->pContext );
m_TimerEntryPool.Release( &m_TimerEntryPool, pTimerEntry );
}
//**********************************************************************
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::CompleteOutstandingSends - check for completed sends and
// indicate send completion for them.
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::CompleteOutstandingSends"
void CThreadPool::CompleteOutstandingSends( void )
{
CBilink *pCurrentOutstandingWrite;
CBilink WritesToBeProcessed;
WritesToBeProcessed.Initialize();
LockWriteData();
//
// Loop through the list out outstanding sends. Any completed sends are
// removed from the list and processed after we release the write data lock.
//
pCurrentOutstandingWrite = m_OutstandingWriteList.GetNext();
while ( pCurrentOutstandingWrite != &m_OutstandingWriteList )
{
CWriteIOData *pWriteIOData;
DWORD dwFlags;
//
// note this send and advance pointer to the next pending send
//
pWriteIOData = CWriteIOData::WriteDataFromBilink( pCurrentOutstandingWrite );
pCurrentOutstandingWrite = pCurrentOutstandingWrite->GetNext();
if ( pWriteIOData->Win9xOperationPending() != FALSE )
{
if ( p_WSAGetOverlappedResult( pWriteIOData->SocketPort()->GetSocket(),
pWriteIOData->Overlap(),
&pWriteIOData->m_dwOverlappedBytesSent,
FALSE,
&dwFlags
) != FALSE )
{
//
// Overlapped results will complete with success and zero bytes
// transferred when the overlapped structure is checked BEFORE
// the operation has really been submitted. This is a possibility
// with the current code. To combat this, check the sent bytes
// for zero (we'll never send zero bytes).
//
if ( pWriteIOData->m_dwOverlappedBytesSent == 0 )
{
goto SkipSendCompletion;
}
pWriteIOData->m_Win9xSendHResult = DPN_OK;
pWriteIOData->m_dwOverlappedBytesSent = 0;
}
else
{
DWORD dwWSAError;
dwWSAError = p_WSAGetLastError();
switch( dwWSAError )
{
//
// this I/O operation is incomplete, don't send notification to the user
//
case ERROR_IO_PENDING:
case WSA_IO_INCOMPLETE:
{
goto SkipSendCompletion;
break;
}
//
// WSAENOTSOCK = the socket has been closed, most likely
// as a result of a command completing or being cancelled.
//
case WSAENOTSOCK:
{
pWriteIOData->m_Win9xSendHResult = DPNERR_USERCANCEL;
break;
}
//
// other error, stop and look
//
default:
{
DNASSERT( FALSE );
pWriteIOData->m_Win9xSendHResult = DPNERR_GENERIC;
DisplayWinsockError( 0, dwWSAError );
break;
}
}
}
DNASSERT( pWriteIOData->Win9xOperationPending() != FALSE );
pWriteIOData->SetWin9xOperationPending( FALSE );
pWriteIOData->m_OutstandingWriteListLinkage.RemoveFromList();
pWriteIOData->m_OutstandingWriteListLinkage.InsertBefore( &WritesToBeProcessed );
}
SkipSendCompletion:
//
// the following line is present to prevent the compiler from whining
// about a blank line
//
;
}
UnlockWriteData();
//
// process all writes that have been pulled to the side.
//
while ( WritesToBeProcessed.GetNext() != &WritesToBeProcessed )
{
BOOL fIOServiced;
CWriteIOData *pTempWrite;
CSocketPort *pSocketPort;
pTempWrite = CWriteIOData::WriteDataFromBilink( WritesToBeProcessed.GetNext() );
pTempWrite->m_OutstandingWriteListLinkage.RemoveFromList();
pSocketPort = pTempWrite->SocketPort();
DNASSERT( pSocketPort != NULL );
fIOServiced = pSocketPort->SendFromWriteQueue();
pSocketPort->SendComplete( pTempWrite, pTempWrite->m_Win9xSendHResult );
pSocketPort->DecRef();
}
}
//**********************************************************************
#endif // WIN95
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::CompleteOutstandingReceives - check for completed receives and
// indicate completion for them.
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::CompleteOutstandingReceives"
void CThreadPool::CompleteOutstandingReceives( void )
{
CBilink *pCurrentOutstandingRead;
CBilink ReadsToBeProcessed;
#ifdef DEBUG
DWORD dwNumReads = 0;
DWORD dwNumReadsCompleteSuccess = 0;
DWORD dwNumReadsCompleteFailure = 0;
#endif // DEBUG
ReadsToBeProcessed.Initialize();
LockReadData();
//
// Loop through the list of outstanding reads and pull out the ones that need
// to be serviced. We don't want to service them while the read data lock
// is taken.
//
pCurrentOutstandingRead = m_OutstandingReadList.GetNext();
while ( pCurrentOutstandingRead != &m_OutstandingReadList )
{
CReadIOData *pReadIOData;
DWORD dwFlags;
pReadIOData = CReadIOData::ReadDataFromBilink( pCurrentOutstandingRead );
pCurrentOutstandingRead = pCurrentOutstandingRead->GetNext();
#ifdef DEBUG
dwNumReads++;
#endif // DEBUG
//
// Make sure this operation is really pending before attempting to check
// for completion. It's possible that the read was added to the list, but
// we haven't actually called Winsock yet.
//
if ( pReadIOData->Win9xOperationPending() != FALSE )
{
if ( p_WSAGetOverlappedResult( pReadIOData->SocketPort()->GetSocket(),
pReadIOData->Overlap(),
&pReadIOData->m_dwOverlappedBytesReceived,
FALSE,
&dwFlags
) != FALSE )
{
//
// Overlapped results will complete with success and zero bytes
// transferred when the overlapped structure is checked BEFORE
// the operation has really been submitted. This is a possibility
// with the current code. To combat this, check the received bytes
// for zero (the return when the overlapped request was checked before
// it was sent) and check the return address (it's possible that someone
// really sent zero bytes).
//
DBG_CASSERT( ERROR_SUCCESS == 0 );
if ( ( pReadIOData->m_dwOverlappedBytesReceived != 0 ) &&
( pReadIOData->m_pSourceSocketAddress->IsUndefinedHostAddress() == FALSE ) )
{
#ifdef DEBUG
dwNumReadsCompleteSuccess++;
#endif // DEBUG
pReadIOData->m_ReceiveWSAReturn = ERROR_SUCCESS;
}
else
{
DPFX(DPFPREP, 8, "Read data 0x%p overlapped bytes received == 0 or source address is undefined host address, ignoring completion.",
pReadIOData);
goto SkipReceiveCompletion;
}
}
else
{
pReadIOData->m_ReceiveWSAReturn = p_WSAGetLastError();
switch( pReadIOData->m_ReceiveWSAReturn )
{
//
// If this I/O operation is incomplete, don't send notification to the user.
//
case WSA_IO_INCOMPLETE:
{
DPFX(DPFPREP, 10, "Read data 0x%p still incomplete (0x%lx, macro result = %i).",
pReadIOData, (pReadIOData->Overlap()->Internal),
HasOverlappedIoCompleted(pReadIOData->Overlap()));
goto SkipReceiveCompletion;
break;
}
//
// socket was closed with an outstanding read, no problem
// Win9x reports 'WSAENOTSOCK'
// WinNT reports 'ERROR_OPERATION_ABORTED'
//
// If this is an indication that the connection was reset,
// pass it on to the socket port so it can issue another
// read
//
case ERROR_OPERATION_ABORTED:
case WSAENOTSOCK:
case WSAECONNRESET:
{
DPFX(DPFPREP, 1, "Read data 0x%p failed with closing err %u/0x%lx.",
pReadIOData, pReadIOData->m_ReceiveWSAReturn,
pReadIOData->m_ReceiveWSAReturn);
break;
}
default:
{
DPFX(DPFPREP, 0, "Read data 0x%p failed, err %u/0x%lx!",
pReadIOData, pReadIOData->m_ReceiveWSAReturn,
pReadIOData->m_ReceiveWSAReturn);
DisplayWinsockError( 0, pReadIOData->m_ReceiveWSAReturn );
// debug me!
DNASSERT( FALSE );
break;
}
}
#ifdef DEBUG
dwNumReadsCompleteFailure++;
#endif // DEBUG
}
DNASSERT( pReadIOData->Win9xOperationPending() != FALSE );
pReadIOData->SetWin9xOperationPending( FALSE );
pReadIOData->m_OutstandingReadListLinkage.RemoveFromList();
pReadIOData->m_OutstandingReadListLinkage.InsertBefore( &ReadsToBeProcessed );
}
else
{
DPFX(DPFPREP, 7, "Read data 0x%p is not marked as pending, so not checking for completion.",
pReadIOData);
}
SkipReceiveCompletion:
//
// the following line is present to prevent the compiler from whining
// about a blank line
//
;
}
UnlockReadData();
#ifdef DEBUG
DPFX(DPFPREP, 9, "(0x%p) %u reads, %u completed successfully, %u completed with failure.",
this, dwNumReads, dwNumReadsCompleteSuccess, dwNumReadsCompleteFailure);
dwNumReads = dwNumReadsCompleteSuccess + dwNumReadsCompleteFailure;
#endif // DEBUG
//
// loop through the list of reads that have completed and dispatch them
//
while ( ReadsToBeProcessed.GetNext() != &ReadsToBeProcessed )
{
CReadIOData *pTempRead;
CSocketPort *pSocketPort;
pTempRead = CReadIOData::ReadDataFromBilink( ReadsToBeProcessed.GetNext() );
pTempRead->m_OutstandingReadListLinkage.RemoveFromList();
#ifdef DEBUG
DNASSERT(dwNumReads > 0);
dwNumReads--;
#endif // DEBUG
pSocketPort = pTempRead->SocketPort();
DNASSERT( pSocketPort != NULL );
pSocketPort->Winsock2ReceiveComplete( pTempRead );
}
DNASSERT(dwNumReads == 0);
}
//**********************************************************************
#endif // WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::HandleNATHelpUpdate - handle a NAT Help update event
//
// Entry: Timer interval if update is occurring periodically, or
// NULL if a triggered event.
// This function may take a while, because updating NAT Help
// can block.
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::HandleNATHelpUpdate"
void CThreadPool::HandleNATHelpUpdate( DN_TIME * const pTimerInterval )
{
HRESULT hr;
DWORD dwTemp;
DPNHCAPS dpnhcaps;
DN_TIME NATHelpRetryTime;
BOOL fModifiedRetryInterval;
DN_TIME FirstUpdateTime;
DN_TIME CurrentTime;
DN_TIME Temp;
DWORD dwNumGetCaps = 0;
DNASSERT(IsNATHelpLoaded());
Lock();
//
// Prevent multiple threads from trying to update NAT Help status at the same
// time. If we're a duplicate, just bail.
//
if (m_dwNATHelpUpdateThreadID != 0)
{
DPFX(DPFPREP, 1, "Thread %u/0x%x already handling NAT Help update, not processing again (thread pool = 0x%p, timer = 0x%p).",
m_dwNATHelpUpdateThreadID, m_dwNATHelpUpdateThreadID, this, pTimerInterval);
Unlock();
return;
}
m_dwNATHelpUpdateThreadID = GetCurrentThreadId();
if (! m_fNATHelpTimerJobSubmitted)
{
DPFX(DPFPREP, 1, "Handling NAT Help update without a NAT refresh timer job submitted (thread pool = 0x%p).",
this);
DNASSERT(pTimerInterval == NULL);
}
Unlock();
DPFX(DPFPREP, 6, "Beginning thread pool 0x%p NAT Help update.", this);
//
// Initialize the timer values.
//
NATHelpRetryTime.Time32.TimeHigh = 0;
NATHelpRetryTime.Time32.TimeLow = -1;
FirstUpdateTime.Time32.TimeHigh = 0;
FirstUpdateTime.Time32.TimeLow = 0;
for(dwTemp = 0; dwTemp < MAX_NUM_DIRECTPLAYNATHELPERS; dwTemp++)
{
if (g_papNATHelpObjects[dwTemp] != NULL)
{
ZeroMemory(&dpnhcaps, sizeof(dpnhcaps));
dpnhcaps.dwSize = sizeof(dpnhcaps);
hr = IDirectPlayNATHelp_GetCaps(g_papNATHelpObjects[dwTemp],
&dpnhcaps,
DPNHGETCAPS_UPDATESERVERSTATUS);
switch (hr)
{
case DPNH_OK:
{
//
// See if this is the shortest interval.
//
if (dpnhcaps.dwRecommendedGetCapsInterval < NATHelpRetryTime.Time32.TimeLow)
{
NATHelpRetryTime.Time32.TimeLow = dpnhcaps.dwRecommendedGetCapsInterval;
}
break;
}
case DPNHSUCCESS_ADDRESSESCHANGED:
{
DPFX(DPFPREP, 1, "NAT Help index %u indicated public addresses changed.",
dwTemp);
//
// We don't actually store any public address information,
// we query it each time. Therefore we don't need to
// actually do anything with the change notification.
//
//
// See if this is the shortest interval.
//
if (dpnhcaps.dwRecommendedGetCapsInterval < NATHelpRetryTime.Time32.TimeLow)
{
NATHelpRetryTime.Time32.TimeLow = dpnhcaps.dwRecommendedGetCapsInterval;
}
break;
}
case DPNHERR_OUTOFMEMORY:
{
//
// This should generally only happen in stress. We'll
// continue on to other NAT help objects, and hope we
// aren't totally hosed.
//
DPFX(DPFPREP, 0, "NAT Help index %u returned out-of-memory error! Continuing.",
dwTemp);
break;
}
default:
{
//
// Some other unknown error occurred.
//
DNASSERT(! "Unknown error returned from IDirectPlayNATHelp_GetCaps!");
break;
}
}
//
// Save the current time, if this is the first GetCaps.
//
if (dwNumGetCaps == 0)
{
DNTimeGet(&FirstUpdateTime);
}
dwNumGetCaps++;
}
else
{
//
// No DPNATHelp object in that slot.
//
}
}
//
// Assert that at least one NAT Help object is loaded.
//
DNASSERT(dwNumGetCaps > 0);
DNTimeGet(&CurrentTime);
//
// If we don't have an infinite timer, we may need to make some adjustments.
//
if (NATHelpRetryTime.Time32.TimeLow != -1)
{
DN_TIME TimeElapsed;
//
// Find out how much time has elapsed since the first GetCaps completed.
//
DNTimeSubtract(&CurrentTime, &FirstUpdateTime, &TimeElapsed);
//
// Remove it from the retry interval.
//
DNTimeSubtract(&NATHelpRetryTime, &TimeElapsed, &Temp);
memcpy(&NATHelpRetryTime, &Temp, sizeof(NATHelpRetryTime));
}
else
{
DPFX(DPFPREP, 3, "NAT Help refresh timer for thread pool 0x%p is set to INFINITE.",
this);
//
// Make sure the high DWORD is -1 as well.
//
NATHelpRetryTime.Time32.TimeHigh = -1;
}
//
// Modify the next time when we should refresh the NAT Help information based
// on the reported recommendation.
//
if (pTimerInterval != NULL)
{
DPFX(DPFPREP, 6, "Modifying NAT Help refresh timer for thread pool 0x%p in place (was %u ms, changing to %u).",
this, pTimerInterval->Time32.TimeLow, NATHelpRetryTime.Time32.TimeLow);
pTimerInterval->Time32.TimeLow = NATHelpRetryTime.Time32.TimeLow;
}
else
{
//
// Add the interval to the current time to find the new retry time.
//
DNTimeAdd(&CurrentTime, &NATHelpRetryTime, &Temp);
DPFX(DPFPREP, 6, "Modifying NAT Help refresh timer for thread pool 0x%p to run at offset %u (in %u ms).",
this, Temp.Time32.TimeLow, NATHelpRetryTime.Time32.TimeLow);
//
// Try to modify the existing timer job. There is a race where the
// first one may not have even been submitted yet. In that case,
// don't try to resubmit it here. Let the other thread submit it,
// since it assumes that no one else has already.
//
fModifiedRetryInterval = ModifyTimerJobNextRetryTime(this, &Temp);
if (! fModifiedRetryInterval)
{
DPFX(DPFPREP, 0, "Unable to modify NAT Help refresh timer (thread pool 0x%p)!",
this);
}
}
//
// Now that we're done handling the update, let other threads do what they
// want.
//
Lock();
DNASSERT(m_dwNATHelpUpdateThreadID == GetCurrentThreadId());
m_dwNATHelpUpdateThreadID = 0;
Unlock();
}
//**********************************************************************
#ifdef DEBUG
//**********************************************************************
// ------------------------------
// CThreadPool::DebugPrintOutstandingReads - print all current outstanding reads
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::DebugPrintOutstandingReads"
void CThreadPool::DebugPrintOutstandingReads( void )
{
CBilink * pBilink;
CReadIOData * pReadData;
CSocketPort * pSocketPort;
DPFX(DPFPREP, 4, "Thread pool 0x%p outstanding reads:", this);
this->LockReadData();
pBilink = this->m_OutstandingReadList.GetNext();
while (pBilink != &this->m_OutstandingReadList)
{
pReadData = CReadIOData::ReadDataFromBilink(pBilink);
pBilink = pBilink->GetNext();
pSocketPort = pReadData->SocketPort();
if (pSocketPort != NULL)
{
DPFX(DPFPREP, 4, " Read data 0x%p, socketport = 0x%p, SPData = 0x%p, internal = 0x%lx, complete = %i",
pReadData, pSocketPort, pSocketPort->GetSPData(),
(pReadData->Overlap()->Internal),
HasOverlappedIoCompleted(pReadData->Overlap()));
}
else
{
DPFX(DPFPREP, 4, " Read data 0x%p, NULL socketport, internal = 0x%lx, complete = %i",
pReadData, (pReadData->Overlap()->Internal),
HasOverlappedIoCompleted(pReadData->Overlap()));
}
}
this->UnlockReadData();
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::DebugPrintOutstandingWrites - print all current outstanding writes
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::DebugPrintOutstandingWrites"
void CThreadPool::DebugPrintOutstandingWrites( void )
{
CBilink * pBilink;
CWriteIOData * pWriteData;
CSocketPort * pSocketPort;
DPFX(DPFPREP, 4, "Thread pool 0x%p outstanding writes:", this);
this->LockWriteData();
pBilink = this->m_OutstandingWriteList.GetNext();
while (pBilink != &this->m_OutstandingWriteList)
{
pWriteData = CWriteIOData::WriteDataFromBilink(pBilink);
pBilink = pBilink->GetNext();
pSocketPort = pWriteData->SocketPort();
if (pSocketPort != NULL)
{
DPFX(DPFPREP, 4, " Write data 0x%p, socketport = 0x%p, SPData = 0x%p, internal = 0x%lx, complete = %i",
pWriteData, pSocketPort, pSocketPort->GetSPData(),
(pWriteData->Overlap()->Internal),
HasOverlappedIoCompleted(pWriteData->Overlap()));
}
else
{
DPFX(DPFPREP, 4, " Write data 0x%p, NULL socketport, internal = 0x%lx, complete = %i",
pWriteData, (pWriteData->Overlap()->Internal),
HasOverlappedIoCompleted(pWriteData->Overlap()));
}
}
this->UnlockWriteData();
}
//**********************************************************************
#endif // DEBUG
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::PrimaryWin9xThread - main thread to do everything that the SP is
// supposed to do under Win9x.
//
// Entry: Pointer to startup parameter
//
// Exit: Error Code
//
// Note: The startup parameter is allocated for this thread and must be
// deallocated by this thread when it exits
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::PrimaryWin9xThread"
DWORD WINAPI CThreadPool::PrimaryWin9xThread( void *pParam )
{
WIN9X_CORE_DATA CoreData;
DN_TIME CurrentTime;
DWORD dwMaxWaitTime;
DN_TIME DeltaT;
BOOL fComInitialized;
CThreadPool *const pThisThreadPool = static_cast<WIN9X_THREAD_DATA *>( pParam )->pThisThreadPool;
FD_SET *const pSocketSet = &pThisThreadPool->m_SocketSet;
DNASSERT( pParam != NULL );
DNASSERT( pThisThreadPool != NULL );
DNASSERT( pSocketSet != NULL );
DPFX(DPFPREP, 4, "Entering [0x%p]", pParam);
//
// initialize
//
memset( &CoreData, 0x00, sizeof CoreData );
fComInitialized = FALSE;
//
// before we do anything we need to make sure COM is happy
//
switch ( COM_CoInitialize( NULL ) )
{
//
// no problem
//
case S_OK:
{
fComInitialized = TRUE;
break;
}
//
// COM already initialized, huh?
//
case S_FALSE:
{
DNASSERT( FALSE );
fComInitialized = TRUE;
break;
}
//
// COM init failed!
//
default:
{
DPFX(DPFPREP, 0, "Primary Win9x thread failed to initialize COM!" );
DNASSERT( FALSE );
break;
}
}
//
// Clear socket data. Since we need to correlate a CSocketPort with a SOCKET,
// we're going to manage the FD_SET ourselves. See Winsock.h for the FD_SET
// structure definition.
//
DBG_CASSERT( OFFSETOF( FD_SET, fd_count ) == 0 );
DNASSERT( CoreData.fTimerJobsActive == FALSE );
//
// set enums to happen infinitely in the future
//
memset( &CoreData.NextTimerJobTime, 0xFF, sizeof( CoreData.NextTimerJobTime ) );
//
// set wait handles
//
CoreData.hWaitHandles[ EVENT_INDEX_STOP_ALL_THREADS ] = pThisThreadPool->m_hStopAllThreads;
CoreData.hWaitHandles[ EVENT_INDEX_PENDING_JOB ] = pThisThreadPool->m_JobQueue.GetPendingJobHandle();
CoreData.hWaitHandles[ EVENT_INDEX_WINSOCK_2_SEND_COMPLETE ] = pThisThreadPool->GetWinsock2SendCompleteEvent();
CoreData.hWaitHandles[ EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE ] = pThisThreadPool->GetWinsock2ReceiveCompleteEvent();
CoreData.hWaitHandles[ EVENT_INDEX_NATHELP_UPDATE ] = INVALID_HANDLE_VALUE;
DNASSERT( CoreData.hWaitHandles[ EVENT_INDEX_STOP_ALL_THREADS ] != NULL );
DNASSERT( CoreData.hWaitHandles[ EVENT_INDEX_PENDING_JOB ] != NULL );
DNASSERT( CoreData.hWaitHandles[ EVENT_INDEX_WINSOCK_2_SEND_COMPLETE ] != NULL );
DNASSERT( CoreData.hWaitHandles[ EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE ] != NULL );
//
// go until we're told to stop
//
CoreData.fLooping = TRUE;
while ( CoreData.fLooping != FALSE )
{
DWORD dwWaitReturn;
//
// Update the job time so we know how long to wait. We can
// only get here if a socket was just added to the socket list, or
// we've been servicing sockets.
//
DNTimeGet( &CurrentTime );
if ( DNTimeCompare( &CurrentTime, &CoreData.NextTimerJobTime ) >= 0 )
{
pThisThreadPool->LockTimerData();
CoreData.fTimerJobsActive = pThisThreadPool->ProcessTimerJobs( &pThisThreadPool->m_TimerJobList,
&CoreData.NextTimerJobTime );
if ( CoreData.fTimerJobsActive != FALSE )
{
DPFX(DPFPREP, 9, "There are active jobs left with Winsock1 sockets active." );
}
pThisThreadPool->UnlockTimerData();
}
DNTimeSubtract( &CoreData.NextTimerJobTime, &CurrentTime, &DeltaT );
//
// Note that data is lost on 64 bit machines.
//
dwMaxWaitTime = static_cast<DWORD>( SaturatedWaitTime( DeltaT ) );
//
// Check for Winsock1 sockets. If there are some around, do a quick poll
// of them to check of I/O before entering the main Winsock2 loop for
// the real timing.
//
pThisThreadPool->Lock();
if ( pSocketSet->fd_count != 0 )
{
pThisThreadPool->Unlock();
//
// if there is Winsock1 I/O that gets serviced, loop immediately. If
// there were no Winsock1 sockets serviced, pause before polling again.
//
if ( pThisThreadPool->CheckWinsock1IO( pSocketSet ) != FALSE )
{
dwMaxWaitTime = 0;
}
else
{
if ( g_dwSelectTimeSlice < dwMaxWaitTime )
{
dwMaxWaitTime = g_dwSelectTimeSlice;
}
}
}
else
{
pThisThreadPool->Unlock();
}
//
// Check Winsock2 sockets.
//
dwWaitReturn = WaitForMultipleObjectsEx( (LENGTHOF( CoreData.hWaitHandles ) - 1), // count of handles except the NATHelp event
CoreData.hWaitHandles, // handles to wait on
FALSE, // don't wait for all to be signalled
dwMaxWaitTime, // wait timeout
TRUE // we're alertable for APCs
);
switch ( dwWaitReturn )
{
//
// timeout, don't do anything, we'll probably process timer jobs on
// the next loop
//
case WAIT_TIMEOUT:
{
break;
}
case ( WAIT_OBJECT_0 + EVENT_INDEX_PENDING_JOB ):
case ( WAIT_OBJECT_0 + EVENT_INDEX_STOP_ALL_THREADS ):
case ( WAIT_OBJECT_0 + EVENT_INDEX_WINSOCK_2_SEND_COMPLETE ):
case ( WAIT_OBJECT_0 + EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE ):
{
pThisThreadPool->ProcessWin9xEvents( &CoreData, THREAD_TYPE_PRIMARY_WIN9X );
break;
}
//
// There are I/O completion routines scheduled on this thread.
// This is not a good thing!
//
case WAIT_IO_COMPLETION:
{
DPFX(DPFPREP, 1, "WARNING: APC was serviced on the primary Win9x IO service thread! What is the application doing??" );
break;
}
//
// wait failed
//
case WAIT_FAILED:
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Primary Win9x thread wait failed!" );
DisplayDNError( 0, dwError );
break;
}
//
// problem
//
default:
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Primary Win9x thread unknown problem in wait!" );
DisplayDNError( 0, dwError );
DNASSERT( FALSE );
break;
}
}
}
pThisThreadPool->DecrementActiveThreadCount();
DNFree( pParam );
if ( fComInitialized != FALSE )
{
COM_CoUninitialize();
fComInitialized = FALSE;
}
DPFX(DPFPREP, 4, "Exiting.");
return 0;
}
//**********************************************************************
#endif // WIN95
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::SecondaryWin9xThread - secondary thread to handle only Win9x
// I/O so developers get bit faster with multithreading issues if they're
// developing on Win9x. This thread will only handle Winsock2 based TCP
// I/O. Winsock 1 is not deemed important enough to hack the rest of the
// code to work with two threads.
//
// Entry: Pointer to startup parameter
//
// Exit: Error Code
//
// Note: The startup parameter is allocated for this thread and must be
// deallocated by this thread when it exits
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::SecondaryWin9xThread"
DWORD WINAPI CThreadPool::SecondaryWin9xThread( void *pParam )
{
WIN9X_CORE_DATA CoreData;
BOOL fComInitialized;
CThreadPool *const pThisThreadPool = static_cast<WIN9X_THREAD_DATA *>( pParam )->pThisThreadPool;
DPFX(DPFPREP, 4, "Entering [0x%p]", pParam);
DNASSERT( pParam != NULL );
DNASSERT( pThisThreadPool != NULL );
//
// initialize
//
memset( &CoreData, 0x00, sizeof CoreData );
fComInitialized = FALSE;
//
// before we do anything we need to make sure COM is happy
//
switch ( COM_CoInitialize( NULL ) )
{
//
// no problem
//
case S_OK:
{
fComInitialized = TRUE;
break;
}
//
// COM already initialized, huh?
//
case S_FALSE:
{
DNASSERT( FALSE );
fComInitialized = TRUE;
break;
}
//
// COM init failed!
//
default:
{
DPFX(DPFPREP, 0, "Secondary Win9x thread failed to initialize COM!");
DNASSERT( FALSE );
break;
}
}
//
// set enums to happen infinitely in the future
//
DNASSERT( CoreData.fTimerJobsActive == FALSE );
memset( &CoreData.NextTimerJobTime, 0xFF, sizeof( CoreData.NextTimerJobTime ) );
//
// set wait handles
//
CoreData.hWaitHandles[ EVENT_INDEX_STOP_ALL_THREADS ] = pThisThreadPool->m_hStopAllThreads;
CoreData.hWaitHandles[ EVENT_INDEX_PENDING_JOB ] = pThisThreadPool->m_JobQueue.GetPendingJobHandle();
CoreData.hWaitHandles[ EVENT_INDEX_WINSOCK_2_SEND_COMPLETE ] = pThisThreadPool->GetWinsock2SendCompleteEvent();
CoreData.hWaitHandles[ EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE ] = pThisThreadPool->GetWinsock2ReceiveCompleteEvent();
CoreData.hWaitHandles[ EVENT_INDEX_NATHELP_UPDATE ] = pThisThreadPool->GetNATHelpUpdateEvent();
DNASSERT( CoreData.hWaitHandles[ EVENT_INDEX_STOP_ALL_THREADS ] != NULL );
DNASSERT( CoreData.hWaitHandles[ EVENT_INDEX_PENDING_JOB ] != NULL );
DNASSERT( CoreData.hWaitHandles[ EVENT_INDEX_WINSOCK_2_SEND_COMPLETE ] != NULL );
DNASSERT( CoreData.hWaitHandles[ EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE ] != NULL );
DNASSERT( CoreData.hWaitHandles[ EVENT_INDEX_NATHELP_UPDATE ] != NULL );
//
// go until we're told to stop
//
CoreData.fLooping = TRUE;
while ( CoreData.fLooping != FALSE )
{
DWORD dwWaitReturn;
//
// Check Winsock2 sockets.
//
dwWaitReturn = WaitForMultipleObjectsEx( LENGTHOF( CoreData.hWaitHandles ), // count of handles
CoreData.hWaitHandles, // handles to wait on
FALSE, // don't wait for all to be signalled
INFINITE, // wait timeout (forever)
TRUE // we're alertable for APCs
);
switch ( dwWaitReturn )
{
//
// timeout, shouldn't ever be here!!
//
case WAIT_TIMEOUT:
{
DNASSERT( FALSE );
break;
}
case ( WAIT_OBJECT_0 + EVENT_INDEX_PENDING_JOB ):
case ( WAIT_OBJECT_0 + EVENT_INDEX_STOP_ALL_THREADS ):
case ( WAIT_OBJECT_0 + EVENT_INDEX_WINSOCK_2_SEND_COMPLETE ):
case ( WAIT_OBJECT_0 + EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE ):
case ( WAIT_OBJECT_0 + EVENT_INDEX_NATHELP_UPDATE ):
{
pThisThreadPool->ProcessWin9xEvents( &CoreData, THREAD_TYPE_SECONDARY_WIN9X );
break;
}
//
// There are I/O completion routines scheduled on this thread.
// This is not a good thing!
//
case WAIT_IO_COMPLETION:
{
DPFX(DPFPREP, 1, "WARNING: APC was serviced on the secondary Win9x IO service thread! What is the application doing??" );
break;
}
//
// wait failed
//
case WAIT_FAILED:
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Secondary Win9x thread wait failed!" );
DisplayDNError( 0, dwError );
break;
}
//
// problem
//
default:
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Secondary Win9x thread unknown problem in wait!" );
DisplayDNError( 0, dwError );
DNASSERT( FALSE );
break;
}
}
}
pThisThreadPool->DecrementActiveThreadCount();
DNFree( pParam );
if ( fComInitialized != FALSE )
{
COM_CoUninitialize();
fComInitialized = FALSE;
}
DPFX(DPFPREP, 4, "Exiting.");
return 0;
}
//**********************************************************************
#endif // WIN95
#ifdef WINNT
//**********************************************************************
// ------------------------------
// CThreadPool::WinNTIOCompletionThread - thread to service I/O completion port
//
// Entry: Pointer to startup parameter
//
// Exit: Error Code
//
// Note: The startup parameter is allocated for this thread and must be
// deallocated by this thread when it exits
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::WinNTIOCompletionThread"
DWORD WINAPI CThreadPool::WinNTIOCompletionThread( void *pParam )
{
IOCOMPLETION_THREAD_DATA *pInput;
BOOL fLooping;
HANDLE hIOCompletionPort;
BOOL fComInitialized;
DPFX(DPFPREP, 4, "Entering [0x%p]", pParam);
DNASSERT( pParam != NULL );
//
// initialize
//
pInput = static_cast<IOCOMPLETION_THREAD_DATA*>( pParam );
DNASSERT( pInput->pThisThreadPool != NULL );
fLooping = TRUE;
hIOCompletionPort = pInput->pThisThreadPool->m_hIOCompletionPort;
DNASSERT( hIOCompletionPort != NULL );
fComInitialized = FALSE;
//
// before we do anything we need to make sure COM is happy
//
switch ( COM_CoInitialize( NULL ) )
{
//
// no problem
//
case S_OK:
{
fComInitialized = TRUE;
break;
}
//
// COM already initialized, huh?
//
case S_FALSE:
{
DNASSERT( FALSE );
fComInitialized = TRUE;
break;
}
//
// COM init failed!
//
default:
{
DNASSERT( FALSE );
DPFX(DPFPREP, 0, "Failed to initialize COM!" );
break;
}
}
//
// go until we're told to stop
//
while ( fLooping != FALSE )
{
BOOL fStatusReturn;
DWORD dwBytesRead;
ULONG_PTR uCompletionKey;
OVERLAPPED *pOverlapped;
// get data from completion port
DNASSERT( hIOCompletionPort != NULL );
fStatusReturn = GetQueuedCompletionStatus( hIOCompletionPort, // handle of completion port
&dwBytesRead, // pointer to number of bytes read
&uCompletionKey, // pointer to completion key
&pOverlapped, // pointer to overlapped structure
INFINITE // wait forever
);
// did we fail miserably?
if ( ( fStatusReturn == FALSE ) && ( pOverlapped == FALSE ) )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem getting item from completion port!" );
DisplayErrorCode( 0, dwError );
}
else
{
// what happened?
switch ( uCompletionKey )
{
// ReadFile or WriteFile completed
case IO_COMPLETION_KEY_IO_COMPLETE:
{
CIOData *pIOData;
DNASSERT( pOverlapped != NULL );
pIOData = CIOData::IODataFromOverlap( pOverlapped );
if ( pIOData->IsWriteOperation() != FALSE )
{
HRESULT hSendResult;
BOOL fDataSent;
CWriteIOData *pWriteData;
CSocketPort *pSocketPort;
if ( fStatusReturn == FALSE )
{
hSendResult = DPNERR_GENERIC;
}
else
{
hSendResult = DPN_OK;
}
pWriteData = static_cast<CWriteIOData*>( pIOData );
fDataSent = pWriteData->SocketPort()->SendFromWriteQueue();
pSocketPort = pWriteData->SocketPort();
pSocketPort->SendComplete( pWriteData, hSendResult );
pSocketPort->DecRef();
}
else
{
DWORD dwError;
CReadIOData *pReadData;
if ( fStatusReturn == FALSE )
{
dwError = GetLastError();
}
else
{
dwError = ERROR_SUCCESS;
}
pReadData = static_cast<CReadIOData*>( pIOData );
pReadData->m_ReceiveWSAReturn = dwError;
pReadData->m_dwOverlappedBytesReceived = dwBytesRead;
pReadData->SocketPort()->Winsock2ReceiveComplete( pReadData );
}
break;
}
//
// This thread is quitting, it's possible that the SP is closing,
// or that the thread pool is being trimmed.
//
case IO_COMPLETION_KEY_SP_CLOSE:
{
DPFX(DPFPREP, 8, "IOCompletion SP_CLOSE" );
fLooping = FALSE;
break;
}
//
// a new job was submitted to the job queue, or the SP is closing from above
//
case IO_COMPLETION_KEY_NEW_JOB:
{
THREAD_POOL_JOB *pJobInfo;
//
// SP is still running, process our job
//
pJobInfo = pInput->pThisThreadPool->GetWorkItem();
if ( pJobInfo != NULL )
{
switch ( pJobInfo->JobType )
{
//
// enum refresh
//
case JOB_REFRESH_TIMER_JOBS:
{
DPFX(DPFPREP, 8, "IOCompletion job REFRESH_TIMER_JOBS" );
DNASSERT( pJobInfo->JobData.JobRefreshTimedJobs.uDummy == 0 );
pInput->pThisThreadPool->WakeNTTimerThread();
break;
}
//
// issue callback for this job
//
case JOB_DELAYED_COMMAND:
{
DPFX(DPFPREP, 8, "IOCompletion job DELAYED_COMMAND" );
DNASSERT( pJobInfo->JobData.JobDelayedCommand.pCommandFunction != NULL );
pJobInfo->JobData.JobDelayedCommand.pCommandFunction( pJobInfo );
break;
}
//
// other job
//
default:
{
DPFX(DPFPREP, 0, "IOCompletion job unknown!" );
DNASSERT( FALSE );
break;
}
}
pJobInfo->JobType = JOB_UNINITIALIZED;
pInput->pThisThreadPool->m_JobPool.Release( &pInput->pThisThreadPool->m_JobPool, pJobInfo );
}
break;
}
//
// NAT Help needs servicing
//
case IO_COMPLETION_KEY_NATHELP_UPDATE:
{
DPFX(DPFPREP, 8, "IOCompletion NATHELP_UPDATE" );
pInput->pThisThreadPool->HandleNATHelpUpdate( NULL );
break;
}
//
// unknown I/O completion message type
//
default:
{
DNASSERT( FALSE );
break;
}
}
}
}
pInput->pThisThreadPool->DecrementActiveNTCompletionThreadCount();
DNFree( pInput );
if ( fComInitialized != FALSE )
{
COM_CoUninitialize();
fComInitialized = FALSE;
}
DPFX(DPFPREP, 4, "Exiting.");
return 0;
}
//**********************************************************************
#endif // WINNT
#ifdef WINNT
//**********************************************************************
// ------------------------------
// CThreadPool::WinNTTimerThread - timer thread for NT
//
// Entry: Pointer to startup parameter
//
// Exit: Error Code
//
// Note: The startup parameter is a static memory chunk and cannot be freed.
// Cleanup of this memory is the responsibility of this thread.
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::WinNTTimerThread"
DWORD WINAPI CThreadPool::WinNTTimerThread( void *pParam )
{
CThreadPool *pThisThreadPool;
BOOL fLooping;
DWORD dwWaitReturn;
DN_TIME NextEnumTime;
HANDLE hEvents[ 2 ];
BOOL fComInitialized;
DPFX(DPFPREP, 4, "Entering [0x%p]", pParam);
DNASSERT( pParam != NULL );
//
// initialize
//
pThisThreadPool = static_cast<CThreadPool*>( pParam );
DNASSERT( pThisThreadPool->m_JobQueue.GetPendingJobHandle() != NULL );
memset( &NextEnumTime, 0, sizeof( NextEnumTime ) );
hEvents[ EVENT_INDEX_STOP_ALL_THREADS ] = pThisThreadPool->m_hStopAllThreads;
hEvents[ EVENT_INDEX_WAKE_NT_TIMER_THREAD ] = pThisThreadPool->m_JobQueue.GetPendingJobHandle();
fComInitialized = FALSE;
//
// before we do anything we need to make sure COM is happy
//
switch ( COM_CoInitialize( NULL ) )
{
//
// no problem
//
case S_OK:
{
fComInitialized = TRUE;
break;
}
//
// COM already initialized, huh?
//
case S_FALSE:
{
DNASSERT( FALSE );
fComInitialized = TRUE;
break;
}
//
// COM init failed!
//
default:
{
DNASSERT( FALSE );
DPFX(DPFPREP, 0, "Failed to initialize COM!" );
break;
}
}
//
// there were no active enums so we want to wait forever for something to
// happen
//
fLooping = TRUE;
//
// go until we're told to stop
//
while ( fLooping != FALSE )
{
DN_TIME CurrentTime;
DN_TIME DeltaT;
DWORD dwMaxWaitTime;
DNTimeGet( &CurrentTime );
if ( DNTimeCompare( &NextEnumTime, &CurrentTime ) <= 0 )
{
//
// acknowledge that we've handled this event and then process the
// enums
//
pThisThreadPool->LockTimerData();
if ( ResetEvent( hEvents[ EVENT_INDEX_WAKE_NT_TIMER_THREAD ] ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Problem resetting event to wake NT timer thread!" );
DisplayErrorCode( 0, dwError );
}
pThisThreadPool->ProcessTimerJobs( &pThisThreadPool->m_TimerJobList, &NextEnumTime );
pThisThreadPool->UnlockTimerData();
}
else
{
DPFX(DPFPREP, 7, "Not time for next enum (next = %u, current = %u)",
NextEnumTime.Time32.TimeLow, CurrentTime.Time32.TimeLow);
}
DNTimeSubtract( &NextEnumTime, &CurrentTime, &DeltaT );
//
// Note that data is lost on 64 bit machines.
//
dwMaxWaitTime = static_cast<DWORD>( pThisThreadPool->SaturatedWaitTime( DeltaT ) );
if (dwMaxWaitTime == INFINITE)
{
DPFX(DPFPREP, 9, "Waiting forever for next timed job.");
}
else
{
DPFX(DPFPREP, 9, "Waiting %u ms until next timed job.", dwMaxWaitTime);
}
dwWaitReturn = WaitForMultipleObjectsEx( LENGTHOF( hEvents ), // number of events
hEvents, // event list
FALSE, // wait for any one event to be signalled
dwMaxWaitTime, // timeout
TRUE // be nice and allow APCs
);
switch ( dwWaitReturn )
{
//
// SP closing
//
case ( WAIT_OBJECT_0 + EVENT_INDEX_STOP_ALL_THREADS ):
{
DPFX(DPFPREP, 8, "NT timer thread thread detected SPClose!" );
fLooping = FALSE;
break;
}
//
// Enum wakeup event, someone added an enum to the list. Clear
// our enum time and go back to the top of the loop where we
// will process enums.
//
case ( WAIT_OBJECT_0 + EVENT_INDEX_WAKE_NT_TIMER_THREAD ):
{
memset( &NextEnumTime, 0x00, sizeof( NextEnumTime ) );
break;
}
//
// Wait timeout. We're probably going to process enums, go back
// to the top of the loop.
//
case WAIT_TIMEOUT:
{
break;
}
//
// wait failed
//
case WAIT_FAILED:
{
DPFX(DPFPREP, 0, "NT timer thread WaitForMultipleObjects failed: 0x%x", dwWaitReturn );
DNASSERT( FALSE );
break;
}
//
// problem
//
default:
{
DNASSERT( FALSE );
break;
}
}
}
DPFX(DPFPREP, 8, "NT timer thread is exiting!" );
pThisThreadPool->LockTimerData();
pThisThreadPool->m_fNTTimerThreadRunning = FALSE;
pThisThreadPool->DecrementActiveThreadCount();
pThisThreadPool->UnlockTimerData();
if ( fComInitialized != FALSE )
{
COM_CoUninitialize();
fComInitialized = FALSE;
}
DPFX(DPFPREP, 4, "Exiting.");
return 0;
}
//**********************************************************************
#endif // WINNT
//**********************************************************************
// ------------------------------
// CThreadPool::DialogThreadProc - thread proc for spawning dialogs
//
// Entry: Pointer to startup parameter
//
// Exit: Error Code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::DialogThreadProc"
DWORD WINAPI CThreadPool::DialogThreadProc( void *pParam )
{
const DIALOG_THREAD_PARAM *pThreadParam;
BOOL fComInitialized;
//
// Initialize COM. If this fails, we'll have problems later.
//
fComInitialized = FALSE;
switch ( COM_CoInitialize( NULL ) )
{
case S_OK:
{
fComInitialized = TRUE;
break;
}
case S_FALSE:
{
DNASSERT( FALSE );
fComInitialized = TRUE;
break;
}
//
// COM init failed!
//
default:
{
DPFX(DPFPREP, 0, "Failed to initialize COM!" );
DNASSERT( FALSE );
break;
}
}
DNASSERT( pParam != NULL );
pThreadParam = static_cast<DIALOG_THREAD_PARAM*>( pParam );
pThreadParam->pDialogFunction( pThreadParam->pContext );
pThreadParam->pThisThreadPool->DecrementActiveThreadCount();
DNFree( pParam );
if ( fComInitialized != FALSE )
{
COM_CoUninitialize();
fComInitialized = FALSE;
}
return 0;
}
//**********************************************************************
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::ProcessWin9xEvents - process Win9x events
//
// Entry: Pointer to core data
// Thread type
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::ProcessWin9xEvents"
void CThreadPool::ProcessWin9xEvents( WIN9X_CORE_DATA *const pCoreData, const THREAD_TYPE ThreadType )
{
DNASSERT( pCoreData != NULL );
//
// If delayed jobs are to be processed, process one. Otherwise sleep and
// let another thread pick up the jobs.
//
switch ( WaitForSingleObject( pCoreData->hWaitHandles[ EVENT_INDEX_PENDING_JOB ], 0 ) )
{
case WAIT_TIMEOUT:
{
break;
}
case WAIT_OBJECT_0:
{
switch ( ThreadType )
{
case THREAD_TYPE_PRIMARY_WIN9X:
{
DPFX(DPFPREP, 8, "Primary Win9x thread has a pending job." );
ProcessWin9xJob( pCoreData );
break;
}
case THREAD_TYPE_SECONDARY_WIN9X:
{
//
// Secondary threads are not allowed to process jobs (it messes
// up enum timing), sleep and let someone else handle the job
//
DPFX(DPFPREP, 10, "Secondary Win9x thread ignoring pending job." );
SleepEx( 0, TRUE );
break;
}
default:
{
DNASSERT( FALSE );
break;
}
}
break;
}
default:
{
DNASSERT( FALSE );
break;
}
}
//
// send complete
//
switch ( WaitForSingleObject( pCoreData->hWaitHandles[ EVENT_INDEX_WINSOCK_2_SEND_COMPLETE ], 0 ) )
{
case WAIT_OBJECT_0:
{
//
// reset the event so it will be signalled again if anything
// completes while we're scanning the pending write list
//
if ( ResetEvent( pCoreData->hWaitHandles[ EVENT_INDEX_WINSOCK_2_SEND_COMPLETE ] ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to reset Winsock2 send event!" );
DisplayErrorCode( 0, dwError );
}
DPFX(DPFPREP, 10, "(0x%p) Reset send event 0x%p.",
this, pCoreData->hWaitHandles[ EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE ]);
CompleteOutstandingSends();
break;
}
case WAIT_TIMEOUT:
{
break;
}
default:
{
DNASSERT( FALSE );
break;
}
}
//
// receive complete
//
switch ( WaitForSingleObject( pCoreData->hWaitHandles[ EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE ], 0 ) )
{
case WAIT_OBJECT_0:
{
//
// reset the event so it will be signalled again if anything
// completes while we're scanning the pending read list
//
if ( ResetEvent( pCoreData->hWaitHandles[ EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE ] ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to reset Winsock2 receive event!" );
DisplayErrorCode( 0, dwError );
}
DPFX(DPFPREP, 10, "(0x%p) Reset receive event 0x%p.",
this, pCoreData->hWaitHandles[ EVENT_INDEX_WINSOCK_2_RECEIVE_COMPLETE ]);
CompleteOutstandingReceives();
break;
}
case WAIT_TIMEOUT:
{
break;
}
default:
{
DNASSERT( FALSE );
break;
}
}
//
// The primary thread is not allowed to handle NAT Help updates since
// they take a long time and thus interfere with processing jobs. Only
// attempt to handle it if this is a secondary thread.
//
if (ThreadType == THREAD_TYPE_SECONDARY_WIN9X)
{
switch ( WaitForSingleObject( pCoreData->hWaitHandles[ EVENT_INDEX_NATHELP_UPDATE ], 0 ) )
{
case WAIT_OBJECT_0:
{
DPFX(DPFPREP, 8, "Secondary Win9x thread handling NAT Help update event." );
//
// Reset the event so it will be signalled again if another update
// is necessary while we're handling this one.
//
if ( ResetEvent( pCoreData->hWaitHandles[ EVENT_INDEX_NATHELP_UPDATE ] ) == FALSE )
{
DWORD dwError;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to reset NAT Help update event!" );
DisplayErrorCode( 0, dwError );
}
DPFX(DPFPREP, 10, "(0x%p) Reset NAT Help event 0x%p.",
this, pCoreData->hWaitHandles[ EVENT_INDEX_NATHELP_UPDATE ]);
HandleNATHelpUpdate( NULL );
break;
}
case WAIT_TIMEOUT:
{
break;
}
default:
{
DNASSERT( FALSE );
break;
}
}
}
else
{
DNASSERT( ThreadType == THREAD_TYPE_PRIMARY_WIN9X );
}
//
// stop all threads
//
switch ( WaitForSingleObject( pCoreData->hWaitHandles[ EVENT_INDEX_STOP_ALL_THREADS ], 0 ) )
{
case WAIT_OBJECT_0:
{
DPFX(DPFPREP, 8, "Win9x thread exit because SP closing." );
pCoreData->fLooping = FALSE;
break;
}
case WAIT_TIMEOUT:
{
break;
}
default:
{
DNASSERT( FALSE );
break;
}
}
}
//**********************************************************************
#endif // WIN95
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::ProcessWin9xJob - process a Win9x job
//
// Entry: Pointer core data
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::ProcessWin9xJob"
void CThreadPool::ProcessWin9xJob( WIN9X_CORE_DATA *const pCoreData )
{
THREAD_POOL_JOB *pJobInfo;
//
// Remove and process a single job from the list. If there is no job, skip
// to the end of the function.
//
pJobInfo = GetWorkItem();
if ( pJobInfo == NULL )
{
goto Exit;
}
switch ( pJobInfo->JobType )
{
//
// enum refresh
//
case JOB_REFRESH_TIMER_JOBS:
{
DPFX(DPFPREP, 8, "WorkThread job REFRESH_TIMER_JOBS" );
DNASSERT( pJobInfo->JobData.JobRefreshTimedJobs.uDummy == 0 );
LockTimerData();
pCoreData->fTimerJobsActive = ProcessTimerJobs( &m_TimerJobList, &pCoreData->NextTimerJobTime );
UnlockTimerData();
if ( pCoreData->fTimerJobsActive != FALSE )
{
DPFX(DPFPREP, 9, "There are active timer jobs left after processing a Win9x REFRESH_TIMER_JOBS." );
}
break;
}
//
// issue callback for this job
//
case JOB_DELAYED_COMMAND:
{
DPFX(DPFPREP, 8, "WorkThread job DELAYED_COMMAND" );
DNASSERT( pJobInfo->JobData.JobDelayedCommand.pCommandFunction != NULL );
pJobInfo->JobData.JobDelayedCommand.pCommandFunction( pJobInfo );
break;
}
//
// other job
//
default:
{
DPFX(DPFPREP, 0, "WorkThread Win9x job unknown!" );
DNASSERT( FALSE );
break;
}
}
DEBUG_ONLY( pJobInfo->JobType = JOB_UNINITIALIZED );
m_JobPool.Release( &m_JobPool, pJobInfo );
Exit:
return;
}
//**********************************************************************
#endif // WIN95
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::CheckWinsock1IO - check the IO status for Winsock1 sockets
//
// Entry: Pointer to sockets to watch
//
// Exit: Boolean indicating whether I/O was serviced
// TRUE = I/O serviced
// FALSE = I/O not serviced
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::CheckWinsock1IO"
BOOL CThreadPool::CheckWinsock1IO( FD_SET *const pWinsock1Sockets )
{
static const TIMEVAL SelectNoTime = { 0 };
BOOL fIOServiced;
INT iSelectReturn;
FD_SET ReadSocketSet;
FD_SET WriteSocketSet;
FD_SET ErrorSocketSet;
//
// Make a local copy of all of the sockets. This isn't totally
// efficient, but it works. Multiplying by active socket count will
// spend half the time in the integer multiply.
//
fIOServiced = FALSE;
Lock();
memcpy( &ReadSocketSet, pWinsock1Sockets, sizeof( ReadSocketSet ) );
memcpy( &WriteSocketSet, pWinsock1Sockets, sizeof( WriteSocketSet ) );
memcpy( &ErrorSocketSet, pWinsock1Sockets, sizeof( ErrorSocketSet ) );
Unlock();
//
// Don't check write sockets here because it's very likely that they're ready
// for service but have no outgoing data and will thrash
//
iSelectReturn = p_select( 0, // compatibility parameter (ignored)
&ReadSocketSet, // sockets to check for read
NULL, // sockets to check for write (none)
&ErrorSocketSet, // sockets to check for error
&SelectNoTime // wait timeout (zero, do an instant check)
);
switch ( iSelectReturn )
{
//
// timeout
//
case 0:
{
break;
}
//
// select got pissed
//
case SOCKET_ERROR:
{
DWORD dwWSAError;
dwWSAError = p_WSAGetLastError();
switch ( dwWSAError )
{
//
// WSAENOTSOCK = This socket was probably closed
//
case WSAENOTSOCK:
{
DPFX(DPFPREP, 1, "Winsock1 reporting 'Not a socket' when selecting read or error sockets!" );
break;
}
//
// WSAEINTR = this operation was interrupted
//
case WSAEINTR:
{
DPFX(DPFPREP, 1, "Winsock1 reporting interrupted operation when selecting read or error sockets!" );
break;
}
//
// other
//
default:
{
DPFX(DPFPREP, 0, "Problem selecting read or error sockets for service!" );
DisplayWinsockError( 0, dwWSAError );
DNASSERT( FALSE );
break;
}
}
break;
}
//
// Check for sockets needing read service and error service.
//
default:
{
fIOServiced |= ServiceWinsock1Sockets( &ReadSocketSet, CSocketPort::Winsock1ReadService );
fIOServiced |= ServiceWinsock1Sockets( &ErrorSocketSet, CSocketPort::Winsock1ErrorService );
break;
}
}
//
// Since writes are likely to be ready, check for them separately
//
iSelectReturn = p_select( 0, // compatibility parameter (ignored)
NULL, // sockets to check for read (don't check reads)
&WriteSocketSet, // sockets to check for write
NULL, // sockets to check for error (don't check errors)
&SelectNoTime // wait timeout (zero, do an instant check)
);
switch ( iSelectReturn )
{
//
// timeout, no write sockets are ready for service
//
case 0:
{
break;
}
//
// select failed
//
case SOCKET_ERROR:
{
DWORD dwWSAError;
dwWSAError = p_WSAGetLastError();
switch ( dwWSAError )
{
//
// this socket was probably closed
//
case WSAENOTSOCK:
{
DPFX(DPFPREP, 1, "Winsock1 reporting 'Not a socket' when selecting write sockets!" );
break;
}
//
// other
//
default:
{
DPFX(DPFPREP, 0, "Problem selecting write sockets for service!" );
DisplayWinsockError( 0, dwWSAError );
DNASSERT( FALSE );
break;
}
}
break;
}
//
// Check for sockets needing write service
//
default:
{
fIOServiced |= ServiceWinsock1Sockets( &WriteSocketSet, CSocketPort::Winsock1WriteService );
break;
}
}
return fIOServiced;
}
//**********************************************************************
#endif // WIN95
#ifdef WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::ServiceWinsock1Sockets - service requests on Winsock1 sockets ports
//
// Entry: Pointer to set of sockets
// Pointer to service function
//
// Exit: Boolean indicating whether I/O was serviced
// TRUE = I/O serviced
// FALSE = I/O not serviced
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::ServiceWinsock1Sockets"
BOOL CThreadPool::ServiceWinsock1Sockets( FD_SET *pSocketSet, PSOCKET_SERVICE_FUNCTION pServiceFunction )
{
BOOL fReturn;
UINT_PTR uWaitingSocketCount;
UINT_PTR uSocketPortCount;
CSocketPort *pSocketPorts[ FD_SETSIZE ];
//
// initialize
//
fReturn = FALSE;
uSocketPortCount = 0;
uWaitingSocketCount = pSocketSet->fd_count;
Lock();
while ( uWaitingSocketCount > 0 )
{
UINT_PTR uIdx;
uWaitingSocketCount--;
uIdx = m_SocketSet.fd_count;
while ( uIdx != 0 )
{
uIdx--;
if ( p___WSAFDIsSet( m_SocketSet.fd_array[ uIdx ], pSocketSet ) != FALSE )
{
//
// this socket is still available, add a reference to the socket
// port and keep it around to be processed outside of the lock
//
pSocketPorts[ uSocketPortCount ] = m_pSocketPorts[ uIdx ];
pSocketPorts[ uSocketPortCount ]->AddRef();
uSocketPortCount++;
uIdx = 0;
}
}
}
Unlock();
while ( uSocketPortCount != 0 )
{
uSocketPortCount--;
//
// call the service function and remove the reference
//
fReturn |= (pSocketPorts[ uSocketPortCount ]->*pServiceFunction)();
pSocketPorts[ uSocketPortCount ]->DecRef();
}
return fReturn;
}
//**********************************************************************
#endif // WIN95
//**********************************************************************
// ------------------------------
// CThreadPool::WorkThreadJob_Alloc - allocate a new job
//
// Entry: Pointer to new entry
//
// Exit: Boolean indicating success
// TRUE = initialization successful
// FALSE = initialization failed
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::WorkThreadJob_Alloc"
BOOL CThreadPool::WorkThreadJob_Alloc( void *pItem )
{
BOOL fReturn;
THREAD_POOL_JOB *pJob;
//
// initialize
//
fReturn = TRUE;
pJob = static_cast<THREAD_POOL_JOB*>( pItem );
DEBUG_ONLY( memset( pJob, 0x00, sizeof( *pJob ) ) );
return fReturn;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::WorkThreadJob_Get - a job is being removed from the pool
//
// Entry: Pointer to job
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::WorkThreadJob_Get"
void CThreadPool::WorkThreadJob_Get( void *pItem )
{
THREAD_POOL_JOB *pJob;
//
// initialize
//
pJob = static_cast<THREAD_POOL_JOB*>( pItem );
DNASSERT( pJob->JobType == JOB_UNINITIALIZED );
//
// cannot ASSERT the the following because the pool manager uses that memory
//
// DNASSERT( pJob->pNext == NULL );
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::WorkThreadJob_Release - a job is being returned to the pool
//
// Entry: Pointer to job
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::WorkThreadJob_Release"
void CThreadPool::WorkThreadJob_Release( void *pItem )
{
THREAD_POOL_JOB *pJob;
DNASSERT( pItem != NULL );
pJob = static_cast<THREAD_POOL_JOB*>( pItem );
DNASSERT( pJob->JobType == JOB_UNINITIALIZED );
pJob->pNext = NULL;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::WorkThreadJob_Dealloc - return job to memory manager
//
// Entry: Pointer to job
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::WorkThreadJob_Dealloc"
void CThreadPool::WorkThreadJob_Dealloc( void *pItem )
{
// don't do anything
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::TimerEntry_Alloc - allocate a new timer job entry
//
// Entry: Pointer to new entry
//
// Exit: Boolean indicating success
// TRUE = initialization successful
// FALSE = initialization failed
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::TimerEntry_Alloc"
BOOL CThreadPool::TimerEntry_Alloc( void *pItem )
{
BOOL fReturn;
TIMER_OPERATION_ENTRY *pTimerEntry;
DNASSERT( pItem != NULL );
//
// initialize
//
fReturn = TRUE;
pTimerEntry = static_cast<TIMER_OPERATION_ENTRY*>( pItem );
DEBUG_ONLY( memset( pTimerEntry, 0x00, sizeof( *pTimerEntry ) ) );
pTimerEntry->pContext = NULL;
pTimerEntry->Linkage.Initialize();
return fReturn;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::TimerEntry_Get - get new timer job entry from pool
//
// Entry: Pointer to new entry
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::TimerEntry_Get"
void CThreadPool::TimerEntry_Get( void *pItem )
{
TIMER_OPERATION_ENTRY *pTimerEntry;
DNASSERT( pItem != NULL );
pTimerEntry = static_cast<TIMER_OPERATION_ENTRY*>( pItem );
pTimerEntry->Linkage.Initialize();
DNASSERT( pTimerEntry->pContext == NULL );
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::TimerEntry_Release - return timer job entry to pool
//
// Entry: Pointer to entry
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::TimerEntry_Release"
void CThreadPool::TimerEntry_Release( void *pItem )
{
TIMER_OPERATION_ENTRY *pTimerEntry;
DNASSERT( pItem != NULL );
pTimerEntry = static_cast<TIMER_OPERATION_ENTRY*>( pItem );
pTimerEntry->pContext= NULL;
DNASSERT( pTimerEntry->Linkage.IsEmpty() != FALSE );
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::TimerEntry_Dealloc - deallocate a timer job entry
//
// Entry: Pointer to entry
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::TimerEntry_Dealloc"
void CThreadPool::TimerEntry_Dealloc( void *pItem )
{
TIMER_OPERATION_ENTRY *pTimerEntry;
DNASSERT( pItem != NULL );
//
// initialize
//
pTimerEntry = static_cast<TIMER_OPERATION_ENTRY*>( pItem );
//
// return associated poiner to write data
//
// can't DNASSERT on Linkage because pool manager stomped on it
// DNASSERT( pEnumEntry->Linkage.IsEmpty() != FALSE );
DNASSERT( pTimerEntry->pContext == NULL );
}
//**********************************************************************