/*========================================================================== * * 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( 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( 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( 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 * 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; 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 * 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; if ( pPool == NULL ) { // // Couldn't create this thread's pool, just release the item // without the pool. // CContextFixedTLPool::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::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::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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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 ); } //**********************************************************************