Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3358 lines
88 KiB

/*==========================================================================
*
* Copyright (C) 1998-2002 Microsoft Corporation. All Rights Reserved.
*
* File: ThreadPool.cpp
* Content: main job thread pool
*
*
* History:
* Date By Reason
* ==== == ======
* 11/25/1998 jtk Created
***************************************************************************/
#include "dnwsocki.h"
//**********************************************************************
// Constant definitions
//**********************************************************************
//
// select polling period and duration for I/O (milliseconds)
//
static const DWORD g_dwSelectTimePeriod = 4; // wait for jobs for 4 ms, then wait for I/O
static const DWORD g_dwSelectTimeSlice = 0; // wait for I/O for 0 ms, then wait for jobs
//**********************************************************************
// Macro definitions
//**********************************************************************
//**********************************************************************
// Structure definitions
//**********************************************************************
#ifndef DPNBUILD_NOSPUI
//
// structure passed to dialog threads
//
typedef struct _DIALOG_THREAD_PARAM
{
DIALOG_FUNCTION *pDialogFunction;
void *pContext;
CThreadPool *pThisThreadPool;
} DIALOG_THREAD_PARAM;
#endif // !DPNBUILD_NOSPUI
//**********************************************************************
// Variable definitions
//**********************************************************************
//**********************************************************************
// Function prototypes
//**********************************************************************
//**********************************************************************
// Function definitions
//**********************************************************************
DWORD WINAPI DPNBlockingJobThreadProc(PVOID pvParameter);
//**********************************************************************
// ------------------------------
// CThreadPool::PoolAllocFunction
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::PoolAllocFunction"
BOOL CThreadPool::PoolAllocFunction( void* pvItem, void* pvContext )
{
CThreadPool* pThreadPool = (CThreadPool*)pvItem;
pThreadPool->m_iRefCount = 0;
pThreadPool->m_fAllowThreadCountReduction = FALSE;
#ifndef DPNBUILD_ONLYONETHREAD
pThreadPool->m_iIntendedThreadCount = 0;
#endif // ! DPNBUILD_ONLYONETHREAD
#ifndef DPNBUILD_ONLYWINSOCK2
pThreadPool->m_uReservedSocketCount = 0;
memset( &pThreadPool->m_SocketSet, 0x00, sizeof( pThreadPool->m_SocketSet ) );
memset( &pThreadPool->m_pSocketPorts, 0x00, sizeof( pThreadPool->m_pSocketPorts ) );
pThreadPool->m_pvTimerDataWinsock1IO = NULL;
pThreadPool->m_uiTimerUniqueWinsock1IO = 0;
pThreadPool->m_fCancelWinsock1IO = FALSE;
#endif // ! DPNBUILD_ONLYWINSOCK2
#ifndef DPNBUILD_NONATHELP
pThreadPool->m_fNATHelpLoaded = FALSE;
pThreadPool->m_fNATHelpTimerJobSubmitted = FALSE;
pThreadPool->m_dwNATHelpUpdateThreadID = 0;
#endif // DPNBUILD_NONATHELP
#if ((defined(WINNT)) && (! defined(DPNBUILD_NOMULTICAST)))
pThreadPool->m_fMadcapLoaded = FALSE;
#endif // WINNT and not DPNBUILD_NOMULTICAST
pThreadPool->m_Sig[0] = 'T';
pThreadPool->m_Sig[1] = 'H';
pThreadPool->m_Sig[2] = 'P';
pThreadPool->m_Sig[3] = 'L';
pThreadPool->m_TimerJobList.Initialize();
#ifndef DPNBUILD_ONLYONETHREAD
pThreadPool->m_blBlockingJobQueue.Initialize();
pThreadPool->m_hBlockingJobThread = NULL;
pThreadPool->m_hBlockingJobEvent = NULL;
#endif // ! DPNBUILD_ONLYONETHREAD
return TRUE;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::PoolDellocFunction
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::PoolDeallocFunction"
void CThreadPool::PoolDeallocFunction( void* pvItem )
{
#ifdef DBG
const CThreadPool* pThreadPool = (CThreadPool*)pvItem;
#ifndef DPNBUILD_ONLYWINSOCK2
DNASSERT( pThreadPool->m_uReservedSocketCount == 0 );
DNASSERT( pThreadPool->m_pvTimerDataWinsock1IO == NULL );
DNASSERT( ! pThreadPool->m_fCancelWinsock1IO );
#endif // ! DPNBUILD_ONLYWINSOCK2
DNASSERT( pThreadPool->m_iRefCount == 0 );
#ifndef DPNBUILD_NONATHELP
DNASSERT( pThreadPool->m_fNATHelpLoaded == FALSE );
DNASSERT( pThreadPool->m_fNATHelpTimerJobSubmitted == FALSE );
DNASSERT( pThreadPool->m_dwNATHelpUpdateThreadID == 0 );
#endif // DPNBUILD_NONATHELP
#if ((defined(WINNT)) && (! defined(DPNBUILD_NOMULTICAST)))
DNASSERT( pThreadPool->m_fMadcapLoaded == FALSE );
#endif // WINNT and not DPNBUILD_NOMULTICAST
DNASSERT( pThreadPool->m_TimerJobList.IsEmpty() != FALSE );
#ifndef DPNBUILD_ONLYONETHREAD
DNASSERT( pThreadPool->m_blBlockingJobQueue.IsEmpty() );
DNASSERT( pThreadPool->m_hBlockingJobThread == NULL );
DNASSERT( pThreadPool->m_hBlockingJobEvent == NULL );
#endif // ! DPNBUILD_ONLYONETHREAD
#endif // DBG
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::Initialize - initialize work threads
//
// Entry: Nothing
//
// Exit: Error Code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::Initialize"
HRESULT CThreadPool::Initialize( void )
{
HRESULT hr;
BOOL fInittedLock = FALSE;
BOOL fInittedTimerDataLock = FALSE;
#ifndef DPNBUILD_ONLYONETHREAD
BOOL fInittedBlockingJobLock = FALSE;
#endif // ! DPNBUILD_ONLYONETHREAD
DPFX(DPFPREP, 4, "(0x%p) Enter", this);
//
// initialize
//
hr = DPN_OK;
//
// initialize critical sections
//
if ( DNInitializeCriticalSection( &m_Lock ) == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DebugSetCriticalSectionRecursionCount( &m_Lock, 0 );
DebugSetCriticalSectionGroup( &m_Lock, &g_blDPNWSockCritSecsHeld ); // separate dpnwsock CSes from the rest of DPlay's CSes
fInittedLock = TRUE;
//
// Create the thread pool work interface
//
#ifdef DPNBUILD_LIBINTERFACE
#if ((defined(DPNBUILD_ONLYONETHREAD)) && (! defined(DPNBUILD_MULTIPLETHREADPOOLS)))
DPTPCF_GetObject(reinterpret_cast<void**>(&m_pDPThreadPoolWork));
hr = S_OK;
#else // ! DPNBUILD_ONLYONETHREAD or DPNBUILD_MULTIPLETHREADPOOLS
hr = DPTPCF_CreateObject(reinterpret_cast<void**>(&m_pDPThreadPoolWork));
#endif // ! DPNBUILD_ONLYONETHREAD or DPNBUILD_MULTIPLETHREADPOOLS
#else // ! DPNBUILD_LIBINTERFACE
hr = COM_CoCreateInstance( CLSID_DirectPlay8ThreadPool,
NULL,
CLSCTX_INPROC_SERVER,
IID_IDirectPlay8ThreadPoolWork,
reinterpret_cast<void**>( &m_pDPThreadPoolWork ),
FALSE );
#endif // ! DPNBUILD_LIBINTERFACE
if ( hr != S_OK )
{
DPFX(DPFPREP, 0, " Failed to create thread pool work interface (err = 0x%lx)!", hr);
goto Failure;
}
if ( DNInitializeCriticalSection( &m_TimerDataLock ) == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DebugSetCriticalSectionRecursionCount( &m_TimerDataLock, 1 );
DebugSetCriticalSectionGroup( &m_TimerDataLock, &g_blDPNWSockCritSecsHeld ); // separate dpnwsock CSes from the rest of DPlay's CSes
fInittedTimerDataLock = TRUE;
DNASSERT( m_fAllowThreadCountReduction == FALSE );
m_fAllowThreadCountReduction = TRUE;
#ifndef DPNBUILD_ONLYONETHREAD
if ( DNInitializeCriticalSection( &m_csBlockingJobLock ) == FALSE )
{
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DebugSetCriticalSectionRecursionCount( &m_csBlockingJobLock, 0 );
DebugSetCriticalSectionGroup( &m_csBlockingJobLock, &g_blDPNWSockCritSecsHeld ); // separate dpnwsock CSes from the rest of DPlay's CSes
fInittedBlockingJobLock = TRUE;
DPFX(DPFPREP, 7, "SetIntendedThreadCount %i", g_iThreadCount);
SetIntendedThreadCount( g_iThreadCount );
#endif // ! DPNBUILD_ONLYONETHREAD
Exit:
DPFX(DPFPREP, 4, "(0x%p) Return [0x%lx]", this, hr);
return hr;
Failure:
#ifndef DPNBUILD_ONLYONETHREAD
if (fInittedBlockingJobLock)
{
DNDeleteCriticalSection(&m_csBlockingJobLock);
fInittedBlockingJobLock = FALSE;
}
#endif // ! DPNBUILD_ONLYONETHREAD
if (fInittedTimerDataLock)
{
DNDeleteCriticalSection(&m_TimerDataLock);
fInittedTimerDataLock = FALSE;
}
if (m_pDPThreadPoolWork != NULL)
{
IDirectPlay8ThreadPoolWork_Release(m_pDPThreadPoolWork);
m_pDPThreadPoolWork = NULL;
}
if (fInittedLock)
{
DNDeleteCriticalSection(&m_Lock);
fInittedLock = FALSE;
}
goto Exit;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::Deinitialize - destroy work threads
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::Deinitialize"
void CThreadPool::Deinitialize( void )
{
DPFX(DPFPREP, 4, "(0x%p) Enter", this );
#ifndef DPNBUILD_ONLYWINSOCK2
//
// Keep trying to cancel the Winsock1 timer. It may fail because it's in the
// process of actively executing, but eventually we will catch it while only
// the timer is active.
//
Lock();
if (m_pvTimerDataWinsock1IO != NULL)
{
HRESULT hr;
DWORD dwInterval;
DPFX(DPFPREP, 1, "Cancelling Winsock 1 I/O timer.");
dwInterval = 10;
do
{
hr = IDirectPlay8ThreadPoolWork_CancelTimer(m_pDPThreadPoolWork,
m_pvTimerDataWinsock1IO,
m_uiTimerUniqueWinsock1IO,
0);
if (hr != DPN_OK)
{
Unlock();
IDirectPlay8ThreadPoolWork_SleepWhileWorking(m_pDPThreadPoolWork,
dwInterval,
0);
dwInterval += 5; // next time wait a bit longer
DNASSERT(dwInterval < 600);
Lock();
}
else
{
m_pvTimerDataWinsock1IO = NULL;
}
}
while (m_pvTimerDataWinsock1IO != NULL);
}
Unlock();
#endif // ! DPNBUILD_ONLYWINSOCK2
#ifndef DPNBUILD_NONATHELP
//
// Stop submitting new NAT help refresh jobs.
//
if ( IsNATHelpTimerJobSubmitted() )
{
//
// Try to cancel the job. It could fail if the timer is in the
// process of firing right now. If it is firing, keep looping
// until we see that the timer has noticed cancellation.
//
DPFX(DPFPREP, 5, "Cancelling NAT Help refresh timer job.");
if (! StopTimerJob( this, DPNERR_USERCANCEL ))
{
DWORD dwInterval;
DPFX(DPFPREP, 4, "Couldn't cancel NAT Help refresh timer job, waiting for completion.");
dwInterval = 10;
while (*((volatile BOOL *) (&m_fNATHelpTimerJobSubmitted)))
{
IDirectPlay8ThreadPoolWork_SleepWhileWorking(m_pDPThreadPoolWork,
dwInterval,
0);
dwInterval += 5; // next time wait a bit longer
if (dwInterval > 500)
{
dwInterval = 500;
}
}
}
else
{
DNASSERT(! m_fNATHelpTimerJobSubmitted);
}
}
#endif // DPNBUILD_NONATHELP
#ifndef DPNBUILD_ONLYONETHREAD
//
// Stop the blocking job thread, if it was running.
//
if (m_hBlockingJobThread != NULL)
{
HRESULT hr;
//
// Queue an item with a NULL callback to signal that the thread
// should quit.
//
hr = SubmitBlockingJob(NULL, NULL);
DNASSERT(hr == DPN_OK);
//
// Wait for the thread to complete.
//
hr = IDirectPlay8ThreadPoolWork_WaitWhileWorking(m_pDPThreadPoolWork,
HANDLE_FROM_DNHANDLE(m_hBlockingJobThread),
0);
DNASSERT(hr == DPN_OK);
DNASSERT(m_blBlockingJobQueue.IsEmpty());
DNCloseHandle(m_hBlockingJobThread);
m_hBlockingJobThread = NULL;
DNCloseHandle(m_hBlockingJobEvent);
m_hBlockingJobEvent = NULL;
}
#endif // ! DPNBUILD_ONLYONETHREAD
#ifndef DPNBUILD_NONATHELP
//
// The refresh timer and blocking jobs should have finished earlier,
// it should be safe to unload NAT help now (if we even loaded it).
//
if ( IsNATHelpLoaded() )
{
UnloadNATHelp();
m_fNATHelpLoaded = FALSE;
}
#endif // DPNBUILD_NONATHELP
#if ((defined(WINNT)) && (! defined(DPNBUILD_NOMULTICAST)))
//
// Unload MADCAP, if we had loaded it.
//
if ( IsMadcapLoaded() )
{
UnloadMadcap();
m_fMadcapLoaded = FALSE;
}
#endif // WINNT and not DPNBUILD_NOMULTICAST
if ( m_pDPThreadPoolWork != NULL )
{
IDirectPlay8ThreadPoolWork_Release(m_pDPThreadPoolWork);
m_pDPThreadPoolWork = NULL;
}
m_fAllowThreadCountReduction = FALSE;
DNDeleteCriticalSection( &m_TimerDataLock );
DNDeleteCriticalSection( &m_Lock );
#ifndef DPNBUILD_ONLYONETHREAD
DNDeleteCriticalSection(&m_csBlockingJobLock);
#endif // ! DPNBUILD_ONLYONETHREAD
DPFX(DPFPREP, 4, "(0x%p) Leave", this );
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// 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"
#if ((! defined(DPNBUILD_NOWINSOCK2)) && (! defined(DPNBUILD_ONLYWINSOCK2)))
CReadIOData *CThreadPool::GetNewReadIOData( READ_IO_DATA_POOL_CONTEXT *const pContext, const BOOL fNeedOverlapped )
#else // DPNBUILD_NOWINSOCK2 or DPNBUILD_ONLYWINSOCK2
CReadIOData *CThreadPool::GetNewReadIOData( READ_IO_DATA_POOL_CONTEXT *const pContext )
#endif // DPNBUILD_NOWINSOCK2 or DPNBUILD_ONLYWINSOCK2
{
CReadIOData * pTempReadData;
#ifndef DPNBUILD_NOWINSOCK2
OVERLAPPED * pOverlapped;
#endif // ! DPNBUILD_NOWINSOCK2
DNASSERT( pContext != NULL );
//
// initialize
//
pTempReadData = NULL;
pContext->pThreadPool = this;
pTempReadData = (CReadIOData*)g_ReadIODataPool.Get( pContext );
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 );
#ifndef DPNBUILD_NOWINSOCK2
#ifndef DPNBUILD_ONLYWINSOCK2
if (! fNeedOverlapped)
{
DNASSERT( pTempReadData->GetOverlapped() == NULL );
}
else
#endif // ! DPNBUILD_ONLYWINSOCK2
{
HRESULT hr;
#ifdef DPNBUILD_ONLYONEPROCESSOR
hr = IDirectPlay8ThreadPoolWork_CreateOverlapped( m_pDPThreadPoolWork,
0,
CSocketPort::Winsock2ReceiveComplete,
pTempReadData,
&pOverlapped,
0 );
if (hr != DPN_OK)
{
DPFX(DPFPREP, 0, "Couldn't create overlapped structure (err = 0x%lx)!", hr);
goto Failure;
}
#else // ! DPNBUILD_ONLYONEPROCESSOR
hr = IDirectPlay8ThreadPoolWork_CreateOverlapped( m_pDPThreadPoolWork,
pContext->dwCPU,
CSocketPort::Winsock2ReceiveComplete,
pTempReadData,
&pOverlapped,
0 );
if (hr != DPN_OK)
{
DPFX(DPFPREP, 0, "Couldn't create overlapped structure for CPU %i (err = 0x%lx)!",
pContext->dwCPU, hr);
goto Failure;
}
#endif // ! DPNBUILD_ONLYONEPROCESSOR
pTempReadData->SetOverlapped( pOverlapped );
}
#endif // ! DPNBUILD_NOWINSOCK2
Exit:
return pTempReadData;
Failure:
if ( pTempReadData != NULL )
{
pTempReadData->DecRef();
pTempReadData = NULL;
}
goto Exit;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// 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 )
{
DNASSERT( pReadData != NULL );
DNASSERT( pReadData->m_pSourceSocketAddress != NULL );
g_ReadIODataPool.Release( pReadData );
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// 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"
#ifdef DPNBUILD_ONLYONEPROCESSOR
HRESULT CThreadPool::SubmitTimerJob( const BOOL fPerformImmediately,
const UINT_PTR uRetryCount,
const BOOL fRetryForever,
const DWORD dwRetryInterval,
const BOOL fIdleWaitForever,
const DWORD dwIdleTimeout,
TIMER_EVENT_CALLBACK *const pTimerCallbackFunction,
TIMER_EVENT_COMPLETE *const pTimerCompleteFunction,
void *const pContext )
#else // ! DPNBUILD_ONLYONEPROCESSOR
HRESULT CThreadPool::SubmitTimerJob( const DWORD dwCPU,
const BOOL fPerformImmediately,
const UINT_PTR uRetryCount,
const BOOL fRetryForever,
const DWORD dwRetryInterval,
const BOOL fIdleWaitForever,
const DWORD dwIdleTimeout,
TIMER_EVENT_CALLBACK *const pTimerCallbackFunction,
TIMER_EVENT_COMPLETE *const pTimerCompleteFunction,
void *const pContext )
#endif // ! DPNBUILD_ONLYONEPROCESSOR
{
HRESULT hr = DPN_OK;
TIMER_OPERATION_ENTRY *pEntry = NULL;
BOOL fTimerDataLocked = FALSE;
#ifdef DPNBUILD_ONLYONEPROCESSOR
DWORD dwCPU = -1;
#endif // DPNBUILD_ONLYONEPROCESSOR
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
//
// allocate new enum entry
//
pEntry = static_cast<TIMER_OPERATION_ENTRY*>( g_TimerEntryPool.Get() );
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, 7, "Created timer entry 0x%p (CPU %i, context 0x%p, immed. %i, tries %u, forever %i, interval %u, timeout %u, forever = %i).",
pEntry, dwCPU, pContext, fPerformImmediately, uRetryCount, fRetryForever, dwRetryInterval, dwIdleTimeout, fIdleWaitForever);
//
// build timer entry block
//
pEntry->pContext = pContext;
pEntry->uRetryCount = uRetryCount;
pEntry->fRetryForever = fRetryForever;
pEntry->dwRetryInterval = dwRetryInterval;
pEntry->dwIdleTimeout = dwIdleTimeout;
pEntry->fIdleWaitForever = fIdleWaitForever;
pEntry->pTimerCallback = pTimerCallbackFunction;
pEntry->pTimerComplete = pTimerCompleteFunction;
pEntry->pThreadPool = this;
pEntry->fCancelling = FALSE;
#ifndef DPNBUILD_ONLYONEPROCESSOR
pEntry->dwCPU = dwCPU;
#endif // ! DPNBUILD_ONLYONEPROCESSOR
pEntry->dwNextRetryTime = GETTIMESTAMP();
if (fPerformImmediately)
{
pEntry->dwNextRetryTime -= 1; // longest possible time, so that the time has already passed
}
else
{
pEntry->dwNextRetryTime += dwRetryInterval;
}
LockTimerData();
fTimerDataLocked = TRUE;
pEntry->pvTimerData = NULL;
pEntry->uiTimerUnique = 0;
if (fPerformImmediately)
{
hr = IDirectPlay8ThreadPoolWork_QueueWorkItem(m_pDPThreadPoolWork,
dwCPU, // CPU
CThreadPool::GenericTimerCallback, // callback
pEntry, // user context
0); // flags
if ( hr != DPN_OK )
{
DPFX(DPFPREP, 0, "Problem queueing immediate work item!" );
DisplayDNError( 0, hr );
goto Failure;
}
}
else
{
hr = IDirectPlay8ThreadPoolWork_ScheduleTimer(m_pDPThreadPoolWork,
dwCPU, // CPU
dwRetryInterval, // delay
CThreadPool::GenericTimerCallback, // callback
pEntry, // user context
&pEntry->pvTimerData, // timer data (returned)
&pEntry->uiTimerUnique, // timer unique(returned)
0); // flags
if ( hr != DPN_OK )
{
DPFX(DPFPREP, 0, "Problem scheduling timer!" );
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();
fTimerDataLocked = FALSE;
Exit:
if ( hr != DPN_OK )
{
DPFX(DPFPREP, 0, "Problem with SubmitTimerJob" );
DisplayDNError( 0, hr );
}
return hr;
Failure:
if ( pEntry != NULL )
{
g_TimerEntryPool.Release( pEntry );
DEBUG_ONLY( pEntry = NULL );
}
if ( fTimerDataLocked != FALSE )
{
UnlockTimerData();
fTimerDataLocked = FALSE;
}
goto Exit;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// 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 = NULL;
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 )
{
HRESULT hr;
//
// Mark the entry as cancelling.
//
pTimerEntry->fCancelling = TRUE;
//
// Make sure an actual timer has been submitted.
//
if (pTimerEntry->pvTimerData != NULL)
{
//
// Attempt to cancel the timer. If it succeeds, we're cool.
//
hr = IDirectPlay8ThreadPoolWork_CancelTimer(m_pDPThreadPoolWork,
pTimerEntry->pvTimerData,
pTimerEntry->uiTimerUnique,
0);
}
else
{
if ((! pTimerEntry->fIdleWaitForever) ||
(pTimerEntry->uRetryCount > 0))
{
//
// Timer hasn't been submitted yet, the completion function should
// notice that it is now cancelling.
//
DPFX(DPFPREP, 1, "Timer for entry 0x%p not submitted yet, reporting that timer will still fire.",
pTimerEntry);
hr = DPNERR_CANNOTCANCEL;
}
else
{
//
// No other timer will ever be submitted because the job is now
// waiting forever.
//
DPFX(DPFPREP, 1, "Entry 0x%p was idling forever, cancelling.",
pTimerEntry);
hr = DPN_OK;
}
}
if (hr != DPN_OK)
{
//
// The processing function is still going to fire. It should notice
// that it needs to complete.
//
}
else
{
//
// 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 cancellable 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();
g_TimerEntryPool.Release( 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,
DWORD const dwNextRetryTime)
{
BOOL fFound;
CBilink * pTempEntry;
TIMER_OPERATION_ENTRY * pTimerEntry;
INT_PTR iResult;
DNASSERT( pContext != NULL );
DPFX(DPFPREP, 7, "Parameters (0x%p, %u)", pContext, dwNextRetryTime);
//
// initialize
//
fFound = FALSE;
LockTimerData();
pTempEntry = m_TimerJobList.GetNext();
while ( pTempEntry != &m_TimerJobList )
{
pTimerEntry = TIMER_OPERATION_ENTRY::TimerOperationFromLinkage( pTempEntry );
if ( pTimerEntry->pContext == pContext )
{
iResult = (int) (pTimerEntry->dwNextRetryTime - dwNextRetryTime);
if (iResult != 0)
{
HRESULT hr;
DWORD dwNextRetryTimeDifference;
DWORD dwNewRetryInterval;
if (iResult < 0)
{
//
// Next time to fire is now later.
//
dwNextRetryTimeDifference = dwNextRetryTime - pTimerEntry->dwNextRetryTime;
dwNewRetryInterval = pTimerEntry->dwRetryInterval + dwNextRetryTimeDifference;
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,
dwNextRetryTimeDifference,
pTimerEntry->dwNextRetryTime,
dwNextRetryTime,
pTimerEntry->dwRetryInterval,
dwNewRetryInterval);
}
else
{
//
// Next time to fire is now earlier.
//
dwNextRetryTimeDifference = pTimerEntry->dwNextRetryTime - dwNextRetryTime;
dwNewRetryInterval = pTimerEntry->dwRetryInterval - dwNextRetryTimeDifference;
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,
dwNextRetryTimeDifference,
pTimerEntry->dwNextRetryTime,
dwNextRetryTime,
pTimerEntry->dwRetryInterval,
dwNewRetryInterval);
}
//
// Force the timer to expire right away if the calculations returned a really
// long delay.
//
if (dwNewRetryInterval > 0x80000000)
{
DPFX(DPFPREP, 1, "Timer 0x%p delay 0x%x/%u (next retry time %u) being set to 0.",
pTimerEntry, dwNewRetryInterval, dwNewRetryInterval, dwNextRetryTime);
pTimerEntry->dwRetryInterval = 0;
pTimerEntry->dwNextRetryTime = GETTIMESTAMP();
}
else
{
pTimerEntry->dwRetryInterval = dwNewRetryInterval;
pTimerEntry->dwNextRetryTime = dwNextRetryTime;
}
//
// Attempt to cancel the existing timer.
//
hr = IDirectPlay8ThreadPoolWork_CancelTimer(m_pDPThreadPoolWork,
pTimerEntry->pvTimerData,
pTimerEntry->uiTimerUnique,
0);
if (hr != DPN_OK)
{
DPFX(DPFPREP, 1, "Couldn't cancel existing timer for entry 0x%p (err = 0x%lx), modifying retry timer only.",
pTimerEntry, hr);
}
else
{
//
// Restart the timer.
//
#ifdef DPNBUILD_ONLYONEPROCESSOR
hr = IDirectPlay8ThreadPoolWork_ScheduleTimer(m_pDPThreadPoolWork,
-1, // pick any CPU
dwNewRetryInterval, // delay
CThreadPool::GenericTimerCallback, // callback
pTimerEntry, // user context
&pTimerEntry->pvTimerData, // timer data (returned)
&pTimerEntry->uiTimerUnique, // timer unique (returned)
0); // flags
#else // ! DPNBUILD_ONLYONEPROCESSOR
hr = IDirectPlay8ThreadPoolWork_ScheduleTimer(m_pDPThreadPoolWork,
pTimerEntry->dwCPU, // use same CPU as before
dwNewRetryInterval, // delay
CThreadPool::GenericTimerCallback, // callback
pTimerEntry, // user context
&pTimerEntry->pvTimerData, // timer data (returned)
&pTimerEntry->uiTimerUnique, // timer unique (returned)
0); // flags
#endif // ! DPNBUILD_ONLYONEPROCESSOR
if (hr != DPN_OK)
{
DPFX(DPFPREP, 0, "Couldn't reschedule timer for entry 0x%p (err = 0x%lx)!",
pTimerEntry, hr);
pTimerEntry->pvTimerData = NULL;
//
// Drop lock while we complete timer.
//
UnlockTimerData();
pTimerEntry->pTimerComplete(hr, pTimerEntry->pContext);
g_TimerEntryPool.Release(pTimerEntry);
LockTimerData();
//
// Drop through...
//
}
}
}
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->dwNextRetryTime,
pTimerEntry->dwRetryInterval);
}
fFound = TRUE;
//
// Terminate loop
//
break;
}
pTempEntry = pTempEntry->GetNext();
}
UnlockTimerData();
DPFX(DPFPREP, 7, "Returning [%i]", fFound);
return fFound;
}
//**********************************************************************
#ifndef DPNBUILD_ONLYONETHREAD
//**********************************************************************
// ------------------------------
// CThreadPool::SubmitBlockingJob - submits a blocking job to the be processed by the blockable thread
// duplicate commands (matching callback and context) are disallowed
//
// Entry: Pointer to callback that executes blocking job
// User context
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::SubmitBlockingJob"
HRESULT CThreadPool::SubmitBlockingJob( BLOCKING_JOB_CALLBACK *const pfnBlockingJobCallback,
void *const pvContext )
{
HRESULT hr;
BLOCKING_JOB * pJob = NULL;
DWORD dwTemp;
BOOL fQueueLocked = FALSE;
CBilink * pBilink;
BLOCKING_JOB * pExistingJob;
//
// allocate new enum entry
//
pJob = (BLOCKING_JOB*) g_BlockingJobPool.Get();
if (pJob == NULL)
{
DPFX(DPFPREP, 0, "Cannot allocate memory for blocking job!" );
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DPFX(DPFPREP, 6, "Created blocking job 0x%p (callback 0x%p, context 0x%p).",
pJob, pfnBlockingJobCallback, pvContext);
pJob->Linkage.Initialize();
pJob->pfnBlockingJobCallback = pfnBlockingJobCallback;
pJob->pvContext = pvContext;
DNEnterCriticalSection(&m_csBlockingJobLock);
fQueueLocked = TRUE;
//
// Start the blocking job thread, if we haven't already.
//
if (m_hBlockingJobThread == NULL)
{
m_hBlockingJobEvent = DNCreateEvent(NULL, FALSE, FALSE, NULL);
if (m_hBlockingJobEvent == NULL)
{
#ifdef DBG
dwTemp = GetLastError();
DPFX(DPFPREP, 0, "Couldn't create blocking job event (err = %u)!", dwTemp);
#endif // DBG
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
m_hBlockingJobThread = DNCreateThread(NULL,
0,
DPNBlockingJobThreadProc,
this,
0,
&dwTemp);
if (m_hBlockingJobThread == NULL)
{
#ifdef DBG
dwTemp = GetLastError();
DPFX(DPFPREP, 0, "Couldn't create blocking job thread (err = %u)!", dwTemp);
#endif // DBG
DNCloseHandle(m_hBlockingJobEvent);
m_hBlockingJobEvent = NULL;
hr = DPNERR_OUTOFMEMORY;
goto Failure;
}
DNASSERT(m_blBlockingJobQueue.IsEmpty());
}
else
{
pBilink = m_blBlockingJobQueue.GetNext();
while (pBilink != &m_blBlockingJobQueue)
{
pExistingJob = CONTAINING_OBJECT(pBilink, BLOCKING_JOB, Linkage);
if ((pExistingJob->pfnBlockingJobCallback == pfnBlockingJobCallback) &&
(pExistingJob->pvContext == pvContext))
{
DPFX(DPFPREP, 1, "Existing blocking job 0x%p matches new job 0x%p, not submitting.",
pExistingJob, pJob);
hr = DPNERR_DUPLICATECOMMAND;
goto Failure;
}
pBilink = pBilink->GetNext();
}
}
//
// Add job to queue.
//
pJob->Linkage.InsertBefore(&m_blBlockingJobQueue);
DNLeaveCriticalSection(&m_csBlockingJobLock);
fQueueLocked = FALSE;
//
// Alert the thread.
//
DNSetEvent( m_hBlockingJobEvent );
hr = DPN_OK;
Exit:
return hr;
Failure:
if (fQueueLocked)
{
DNLeaveCriticalSection(&m_csBlockingJobLock);
fQueueLocked = FALSE;
}
if (pJob != NULL)
{
g_BlockingJobPool.Release(pJob);
pJob = NULL;
}
goto Exit;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::DoBlockingJobs - processes all blocking jobs.
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::DoBlockingJobs"
void CThreadPool::DoBlockingJobs( void )
{
BOOL fRunning = TRUE;
CBilink * pBilink;
BLOCKING_JOB * pJob;
//
// Keep looping until it's time to shutdown.
//
while (fRunning)
{
//
// Wait for more work.
//
DNWaitForSingleObject( m_hBlockingJobEvent, INFINITE );
//
// Keep looping while we have work.
//
do
{
DNEnterCriticalSection(&m_csBlockingJobLock);
pBilink = m_blBlockingJobQueue.GetNext();
if (pBilink == &m_blBlockingJobQueue)
{
//
// Bail out of the inner loop.
//
DNLeaveCriticalSection(&m_csBlockingJobLock);
break;
}
pJob = CONTAINING_OBJECT(pBilink, BLOCKING_JOB, Linkage);
pJob->Linkage.RemoveFromList();
DNLeaveCriticalSection(&m_csBlockingJobLock);
//
// Bail out of both loops if it's the quit job.
//
if (pJob->pfnBlockingJobCallback == NULL)
{
DPFX(DPFPREP, 5, "Recognized quit job 0x%p.", pJob);
g_BlockingJobPool.Release(pJob);
fRunning = FALSE;
break;
}
DPFX(DPFPREP, 6, "Processing blocking job 0x%p (callback 0x%p, context 0x%p).",
pJob, pJob->pfnBlockingJobCallback, pJob->pvContext);
pJob->pfnBlockingJobCallback(pJob->pvContext);
DPFX(DPFPREP, 7, "Returning blocking job 0x%p to pool.", pJob);
g_BlockingJobPool.Release(pJob);
}
while (TRUE);
}
}
//**********************************************************************
#endif // ! DPNBUILD_ONLYONETHREAD
#ifndef DPNBUILD_NOSPUI
//**********************************************************************
// ------------------------------
// 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;
DNHANDLE hDialogThread;
DIALOG_THREAD_PARAM *pThreadParam;
DWORD dwThreadID;
DNASSERT( pDialogFunction != NULL );
DNASSERT( pDialogContext != NULL ); // why would anyone not want a dialog context??
//
// initialize
//
hr = DPN_OK;
pThreadParam = NULL;
//
// create and initialize thread param
//
pThreadParam = static_cast<DIALOG_THREAD_PARAM*>( DNMalloc( sizeof( *pThreadParam ) ) );
if ( pThreadParam == NULL )
{
hr = DPNERR_OUTOFMEMORY;
DPFX(DPFPREP, 0, "Failed to allocate memory for dialog thread!" );
goto Failure;
}
pThreadParam->pDialogFunction = pDialogFunction;
pThreadParam->pContext = pDialogContext;
pThreadParam->pThisThreadPool = this;
//
// create thread
//
hDialogThread = DNCreateThread( 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;
dwError = GetLastError();
DPFX(DPFPREP, 0, "Failed to start dialog thread!" );
DisplayErrorCode( 0, dwError );
goto Failure;
}
if ( DNCloseHandle( 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;
}
//**********************************************************************
#endif // !DPNBUILD_NOSPUI
#ifndef DPNBUILD_ONLYONETHREAD
//**********************************************************************
// ------------------------------
// 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;
DWORD dwThreadPoolCount;
DNASSERT( piThreadCount != NULL );
Lock();
hr = IDirectPlay8ThreadPoolWork_GetThreadCount(m_pDPThreadPoolWork,
-1,
&dwThreadPoolCount,
0);
switch (hr)
{
case DPN_OK:
{
*piThreadCount = dwThreadPoolCount;
DPFX(DPFPREP, 6, "User has explicitly started %u threads.", (*piThreadCount));
break;
}
case DPNSUCCESS_PENDING:
{
if ((IsThreadCountReductionAllowed()) &&
(((DWORD) GetIntendedThreadCount()) > dwThreadPoolCount))
{
*piThreadCount = GetIntendedThreadCount();
DPFX(DPFPREP, 6, "Thread pool not locked down and only %u threads currently started, using intended count of %u.",
dwThreadPoolCount, (*piThreadCount));
}
else
{
*piThreadCount = dwThreadPoolCount;
DPFX(DPFPREP, 6, "Thread pool locked down (%i) or more than %u threads already started, using actual count of %u.",
(! IsThreadCountReductionAllowed()), GetIntendedThreadCount(),
(*piThreadCount));
}
hr = DPN_OK;
break;
}
case DPNERR_NOTREADY:
{
DNASSERT(IsThreadCountReductionAllowed());
*piThreadCount = GetIntendedThreadCount();
DPFX(DPFPREP, 6, "Thread pool does not have a thread count set, using intended count of %u.",
(*piThreadCount));
hr = DPN_OK;
break;
}
default:
{
DPFX(DPFPREP, 0, "Failed getting thread count!");
break;
}
}
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;
DWORD dwThreadCount;
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
{
hr = IDirectPlay8ThreadPoolWork_GetThreadCount(m_pDPThreadPoolWork,
-1,
&dwThreadCount,
0);
switch (hr)
{
case DPN_OK:
case DPNSUCCESS_PENDING:
case DPNERR_NOTREADY:
{
if ( (DWORD) iThreadCount != dwThreadCount )
{
if (hr == DPN_OK)
{
DPFX(DPFPREP, 1, "Thread count already set to %u by user, not changing to %i total.",
dwThreadCount, iThreadCount);
hr = DPNERR_NOTALLOWED;
}
else
{
//
// Artificially prevent thread pool reduction after operations have
// started, to simulate pre-unified-threadpool behavior. Only
// request a thread count change if the new count is greater
// than the old count.
//
if ( (DWORD) iThreadCount > dwThreadCount )
{
hr = IDirectPlay8ThreadPoolWork_RequestTotalThreadCount(m_pDPThreadPoolWork,
(DWORD) iThreadCount,
0);
}
else
{
DPFX(DPFPREP, 4, "Thread pool locked down and already has %u threads, not adjusting to %i.",
dwThreadCount, iThreadCount );
hr = DPN_OK;
}
}
}
else
{
DPFX(DPFPREP, 4, "Thread pool thread count matches (%u).",
dwThreadCount);
hr = DPN_OK;
}
break;
}
default:
{
DPFX(DPFPREP, 0, "Getting current thread count failed!");
break;
}
}
}
Unlock();
return hr;
}
//**********************************************************************
#endif // ! DPNBUILD_ONLYONETHREAD
//**********************************************************************
// ------------------------------
// 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;
#ifndef DPNBUILD_ONLYONETHREAD
LONG iDesiredThreads;
#endif // ! DPNBUILD_ONLYONETHREAD
#ifdef _XBOX
DWORD dwStatus;
DWORD dwInterval;
XNADDR xnaddr;
#endif // _XBOX
#ifdef DBG
DWORD dwStartTime;
#endif // DBG
Lock();
//
// If we haven't already clamped down, do so, and spin up the threads.
//
if ( IsThreadCountReductionAllowed() )
{
m_fAllowThreadCountReduction = FALSE;
#ifdef DPNBUILD_ONLYONETHREAD
DPFX(DPFPREP, 3, "Locking down thread pool." );
#else // ! DPNBUILD_ONLYONETHREAD
iDesiredThreads = GetIntendedThreadCount();
DNASSERT( iDesiredThreads > 0 );
SetIntendedThreadCount( 0 );
DPFX(DPFPREP, 3, "Locking down thread count at %i.", iDesiredThreads );
#endif // ! DPNBUILD_ONLYONETHREAD
#ifndef DPNBUILD_ONLYONETHREAD
#ifdef DBG
dwStartTime = GETTIMESTAMP();
#endif // DBG
//
// Have the thread pool object try to start the requested number of threads.
//
// We'll ignore failure, because we could still operate in DoWork mode even
// when starting the threads fails. It most likely failed because the user
// is in that mode already anyway (DPNERR_ALREADYINITIALIZED).
//
hr = IDirectPlay8ThreadPoolWork_RequestTotalThreadCount(m_pDPThreadPoolWork,
iDesiredThreads,
0);
if (hr != DPN_OK)
{
if (hr != DPNERR_ALREADYINITIALIZED)
{
DPFX(DPFPREP, 0, "Requesting thread count failed (err = 0x%lx)!", hr);
}
//
// Continue...
//
}
#ifdef DBG
DPFX(DPFPREP, 8, "Spent %u ms trying to start %i threads.",
(GETTIMESTAMP() - dwStartTime), iDesiredThreads);
#endif // DBG
#endif // ! DPNBUILD_ONLYONETHREAD
#ifdef _XBOX
#ifdef DBG
dwStartTime = GETTIMESTAMP();
#endif // DBG
#pragma TODO(vanceo, "Use #defines")
//
// Wait until the Ethernet link is active.
//
DPFX(DPFPREP, 1, "Ensuring Ethernet link status is active...");
dwStatus = XNetGetEthernetLinkStatus();
dwInterval = 5;
while (! (dwStatus & XNET_ETHERNET_LINK_ACTIVE))
{
if (dwInterval > 100)
{
DPFX(DPFPREP, 0, "Ethernet link never became ready (status = 0x%x)!",
dwStatus);
hr = DPNERR_NOCONNECTION;
goto Failure;
}
DPFX(DPFPREP, 0, "Ethernet link is not ready (0x%x).", dwStatus);
IDirectPlay8ThreadPoolWork_SleepWhileWorking(m_pDPThreadPoolWork, dwInterval, 0);
dwInterval += 5;
dwStatus = XNetGetEthernetLinkStatus();
}
//
// Wait until a DHCP address has been acquired or we give up trying.
// If we're not using DHCP, this should return something other than
// XNET_GET_XNADDR_PENDING right away.
//
DPFX(DPFPREP, 1, "Waiting for a valid address...");
dwStatus = XNetGetTitleXnAddr(&xnaddr);
dwInterval = 5;
while (dwStatus == XNET_GET_XNADDR_PENDING)
{
if (dwInterval > 225)
{
DPFX(DPFPREP, 0, "Never acquired an address (status = 0x%x)!",
dwStatus);
hr = DPNERR_NOTREADY;
goto Failure;
}
IDirectPlay8ThreadPoolWork_SleepWhileWorking(m_pDPThreadPoolWork, dwInterval, 0);
dwInterval += 5;
dwStatus = XNetGetTitleXnAddr(&xnaddr);
}
if (dwStatus == XNET_GET_XNADDR_NONE)
{
DPFX(DPFPREP, 0, "Couldn't get an address!");
hr = DPNERR_NOCONNECTION;
goto Failure;
}
DPFX(DPFPREP, 1, "Network ready.");
#pragma TODO(vanceo, "Ethernet link status timer?")
#ifdef DBG
DPFX(DPFPREP, 8, "Spent %u ms waiting for network.",
(GETTIMESTAMP() - dwStartTime));
#endif // DBG
#endif // _XBOX
}
else
{
DPFX(DPFPREP, 3, "Thread count already locked down." );
}
//
// If we're here, everything is successful.
//
hr = DPN_OK;
#ifdef _XBOX
Exit:
#endif // _XBOX
Unlock();
return hr;
#ifdef _XBOX
Failure:
//
// The only time we can fail in this function is if we disabled thread
// count reduction. In order to return to the previous state, re-enable
// reduction.
//
m_fAllowThreadCountReduction = TRUE;
goto Exit;
#endif // _XBOX
}
//**********************************************************************
#if ((! defined(DPNBUILD_NOMULTICAST)) && (defined(WINNT)))
//**********************************************************************
// ------------------------------
// CThreadPool::EnsureMadcapLoaded - Load the MADCAP API if it hasn't been
// already, and it can be loaded.
//
// Entry: Nothing
//
// Exit: TRUE if MADCAP is loaded, FALSE otherwise
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::EnsureMadcapLoaded"
BOOL CThreadPool::EnsureMadcapLoaded( void )
{
BOOL fReturn;
DPFX(DPFPREP, 7, "Enter");
#ifndef DPNBUILD_NOREGISTRY
if (! g_fDisableMadcapSupport)
#endif // ! DPNBUILD_NOREGISTRY
{
Lock();
if (! IsMadcapLoaded())
{
if ( LoadMadcap() == FALSE )
{
DPFX(DPFPREP, 0, "Failed to load MADCAP API, continuing." );
fReturn = FALSE;
}
else
{
m_fMadcapLoaded = TRUE;
fReturn = TRUE;
}
}
else
{
DPFX(DPFPREP, 4, "MADCAP already loaded." );
fReturn = TRUE;
}
Unlock();
}
#ifndef DPNBUILD_NOREGISTRY
else
{
DPFX(DPFPREP, 0, "Not loading MADCAP API." );
fReturn = FALSE;
}
#endif // ! DPNBUILD_NOREGISTRY
DPFX(DPFPREP, 7, "Return [%i]", fReturn);
return fReturn;
}
#endif // ! DPNBUILD_NOMULTICAST and WINNT
#ifndef DPNBUILD_NONATHELP
//**********************************************************************
// ------------------------------
// CThreadPool::EnsureNATHelpLoaded - Load NAT Help if it hasn't been already.
// This has no return values, so if NAT
// traversal is explicitly disabled, or some
// error occurs, NAT Help will not actually
// get loaded.
//
// Entry: Nothing
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::EnsureNATHelpLoaded"
void CThreadPool::EnsureNATHelpLoaded( void )
{
HRESULT hr;
DWORD dwTemp;
DPNHCAPS dpnhcaps;
DWORD dwNATHelpRetryTime;
#ifdef DBG
DWORD dwStartTime;
#endif // DBG
DPFX(DPFPREP, 7, "Enter");
#ifndef DPNBUILD_NOREGISTRY
if ((! g_fDisableDPNHGatewaySupport) || (! g_fDisableDPNHFirewallSupport))
#endif // ! DPNBUILD_NOREGISTRY
{
Lock();
if ( ! IsNATHelpLoaded() )
{
//
// Attempt to load the NAT helper(s).
//
if ( LoadNATHelp() )
{
m_fNATHelpLoaded = TRUE;
#ifdef DBG
dwStartTime = GETTIMESTAMP();
#endif // DBG
//
// Initialize the timer values.
//
dwNATHelpRetryTime = -1;
//
// Loop through each NAT help object.
//
for(dwTemp = 0; dwTemp < MAX_NUM_DIRECTPLAYNATHELPERS; dwTemp++)
{
if (g_papNATHelpObjects[dwTemp] != NULL)
{
//
// 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.
//
}
else
{
//
// See if this is the shortest interval.
//
if (dpnhcaps.dwRecommendedGetCapsInterval < dwNATHelpRetryTime)
{
dwNATHelpRetryTime = dpnhcaps.dwRecommendedGetCapsInterval;
}
#ifndef DPNBUILD_NOLOCALNAT
//
// Remember if there's a local NAT.
//
if ((dpnhcaps.dwFlags & DPNHCAPSFLAG_GATEWAYPRESENT) &&
(dpnhcaps.dwFlags & DPNHCAPSFLAG_GATEWAYISLOCAL))
{
g_fLocalNATDetectedAtStartup = TRUE;
}
else
{
g_fLocalNATDetectedAtStartup = FALSE;
}
#endif // ! DPNBUILD_NOLOCALNAT
}
}
else
{
//
// No object loaded in this slot.
//
}
}
//
// If there's a retry interval, submit a timer job.
//
if (dwNATHelpRetryTime != -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.",
dwNATHelpRetryTime, this);
DNASSERT(! m_fNATHelpTimerJobSubmitted );
m_fNATHelpTimerJobSubmitted = TRUE;
#ifdef DPNBUILD_ONLYONEPROCESSOR
hr = SubmitTimerJob(FALSE, // don't perform immediately
1, // retry count
TRUE, // retry forever
dwNATHelpRetryTime, // retry timeout
TRUE, // wait forever
0, // idle timeout
CThreadPool::NATHelpTimerFunction, // periodic callback function
CThreadPool::NATHelpTimerComplete, // completion function
this); // context
#else // ! DPNBUILD_ONLYONEPROCESSOR
hr = SubmitTimerJob(-1, // pick any CPU
FALSE, // don't perform immediately
1, // retry count
TRUE, // retry forever
dwNATHelpRetryTime, // retry timeout
TRUE, // wait forever
0, // idle timeout
CThreadPool::NATHelpTimerFunction, // periodic callback function
CThreadPool::NATHelpTimerComplete, // completion function
this); // context
#endif // ! DPNBUILD_ONLYONEPROCESSOR
if (hr != DPN_OK)
{
m_fNATHelpTimerJobSubmitted = FALSE;
DPFX(DPFPREP, 0, "Failed to submit timer job to watch over NAT Help (err = 0x%lx)!", hr );
//
// NAT Help will probably not work correctly, but that won't
// prevent local connections from working. Consider it
// non-fatal.
//
}
}
#ifdef DBG
DPFX(DPFPREP, 8, "Spent %u ms preparing NAT Help.",
(GETTIMESTAMP() - dwStartTime));
#endif // DBG
}
else
{
DPFX(DPFPREP, 0, "Failed to load NAT Help, continuing." );
}
}
else
{
DPFX(DPFPREP, 4, "NAT Help already loaded." );
}
Unlock();
}
#ifndef DPNBUILD_NOREGISTRY
else
{
DPFX(DPFPREP, 0, "Not loading NAT Help." );
}
#endif // ! DPNBUILD_NOREGISTRY
DPFX(DPFPREP, 7, "Leave");
}
//**********************************************************************
// ------------------------------
// CThreadPool::PerformSubsequentNATHelpGetCaps - blocking function to get NAT Help caps again
//
// Entry: Pointer to job information
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CEndpoint::EnumQueryJobCallback"
void CThreadPool::PerformSubsequentNATHelpGetCaps( void * const pvContext )
{
CThreadPool * pThisThreadPool;
DNASSERT( pvContext != NULL );
pThisThreadPool = (CThreadPool*) pvContext;
pThisThreadPool->HandleNATHelpUpdate(NULL);
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// 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 * pThisThreadPool;
DNASSERT( pContext != NULL );
pThisThreadPool = (CThreadPool*) pContext;
DPFX(DPFPREP, 5, "Threadpool 0x%p NAT Help Timer complete.", pThisThreadPool);
pThisThreadPool->m_fNATHelpTimerJobSubmitted = FALSE;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::NATHelpTimerFunction - NAT Help timer job needs service
//
// Entry: Pointer to context
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::NATHelpTimerFunction"
void CThreadPool::NATHelpTimerFunction( void * const pContext )
{
CThreadPool * pThisThreadPool;
DNASSERT( pContext != NULL );
pThisThreadPool = (CThreadPool*) pContext;
//
// Attempt to submit a blocking job to update the NAT capabilites. If it
// fails, we'll just try again later. It might also fail because a previous
// blocking job took so long that there's still a job scheduled in the queue
// already (we disallow duplicates).
//
pThisThreadPool->SubmitBlockingJob(CThreadPool::PerformSubsequentNATHelpGetCaps, pThisThreadPool);
}
//**********************************************************************
#endif // DPNBUILD_NONATHELP
#ifndef DPNBUILD_ONLYWINSOCK2
//**********************************************************************
// ------------------------------
// 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;
DPFX(DPFPREP, 6, "(0x%p) Parameters: (0x%p)", this, pSocketPort);
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_pvTimerDataWinsock1IO == NULL)
{
hr = IDirectPlay8ThreadPoolWork_ScheduleTimer(m_pDPThreadPoolWork,
0, // use CPU 0, we shouldn't have multiple CPUs anyway
g_dwSelectTimePeriod, // delay
CThreadPool::CheckWinsock1IOCallback, // callback
this, // user context
&m_pvTimerDataWinsock1IO, // timer data (returned)
&m_uiTimerUniqueWinsock1IO, // timer unique (returned)
0); // flags
if (hr != DPN_OK)
{
DPFX(DPFPREP, 0, "Couldn't schedule Winsock 1 I/O poll timer!");
goto Failure;
}
}
else
{
//
// It's possible there's still an outstanding lazy cancellation
// attempt. If so, don't try to cancel I/O anymore.
//
if (m_fCancelWinsock1IO)
{
DPFX(DPFPREP, 1, "Retracting lazy cancellation attempt.");
m_fCancelWinsock1IO = FALSE;
}
}
Exit:
Unlock();
DPFX(DPFPREP, 6, "(0x%p) Return: [0x%08x]", this, hr);
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;
DPFX(DPFPREP, 6, "(0x%p) Parameters: (0x%p)", this, pSocketPort);
DNASSERT( pSocketPort != NULL );
Lock();
uIndex = m_SocketSet.fd_count;
DNASSERT( uIndex != 0 );
//
// If this is the last socket, cancel the I/O timer.
//
if ( uIndex == 1 )
{
HRESULT hr;
//
// Keep trying to cancel the Winsock1 timer. It may fail because it's in the
// process of actively executing, but we will set the cancel flag so that the
// timer will eventually notice.
//
DPFX(DPFPREP, 5, "Cancelling Winsock 1 I/O timer.");
DNASSERT(m_pvTimerDataWinsock1IO != NULL);
hr = IDirectPlay8ThreadPoolWork_CancelTimer(m_pDPThreadPoolWork,
m_pvTimerDataWinsock1IO,
m_uiTimerUniqueWinsock1IO,
0);
if (hr != DPN_OK)
{
DPFX(DPFPREP, 2, "Couldn't stop Winsock1 I/O timer, marking for lazy cancellation.");
m_fCancelWinsock1IO = TRUE;
}
else
{
m_pvTimerDataWinsock1IO = NULL;
}
}
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();
DPFX(DPFPREP, 6, "(0x%p) Leave", this);
}
//**********************************************************************
#endif // ! DPNBUILD_ONLYWINSOCK2
#ifdef WIN95
#ifndef DPNBUILD_NOWINSOCK2
#endif // ! DPNBUILD_NOWINSOCK2
#endif // WIN95
#ifndef DPNBUILD_NONATHELP
//**********************************************************************
// ------------------------------
// 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( DWORD * const pdwTimerInterval )
{
HRESULT hr;
DWORD dwTemp;
DPNHCAPS dpnhcaps;
DWORD dwNATHelpRetryTime;
BOOL fModifiedRetryInterval;
DWORD dwFirstUpdateTime;
DWORD dwCurrentTime;
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, pdwTimerInterval);
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(pdwTimerInterval == NULL);
}
Unlock();
DPFX(DPFPREP, 6, "Beginning thread pool 0x%p NAT Help update.", this);
//
// Initialize the timer values.
//
dwNATHelpRetryTime = -1;
dwFirstUpdateTime = GETTIMESTAMP() - 1; // longest possible time
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 < dwNATHelpRetryTime)
{
dwNATHelpRetryTime = 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 < dwNATHelpRetryTime)
{
dwNATHelpRetryTime = 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. Ignore it.
//
DPFX(DPFPREP, 0, "NAT Help index %u returned unknown error 0x%lx! Continuing.",
dwTemp, hr);
break;
}
}
//
// Save the current time, if this is the first GetCaps.
//
if (dwNumGetCaps == 0)
{
dwFirstUpdateTime = GETTIMESTAMP();
}
dwNumGetCaps++;
}
else
{
//
// No DPNATHelp object in that slot.
//
}
}
//
// Assert that at least one NAT Help object is loaded.
//
DNASSERT(dwNumGetCaps > 0);
dwCurrentTime = GETTIMESTAMP();
//
// We may need to make some adjustments to the timer. Either subtract out time
// we've spent fiddling around with other NAT Help interfaces, or make sure the
// time isn't really large because that can screw up time calculations. It's
// not a big deal to wake up after 24 days even though we wouldn't need to
// according to the logic above.
//
if (dwNATHelpRetryTime & 0x80000000)
{
DPFX(DPFPREP, 3, "NAT Help refresh timer for thread pool 0x%p is set to longest possible without going negative.",
this);
dwNATHelpRetryTime = 0x7FFFFFFF;
}
else
{
DWORD dwTimeElapsed;
//
// Find out how much time has elapsed since the first GetCaps completed.
//
dwTimeElapsed = dwCurrentTime - dwFirstUpdateTime;
//
// Remove it from the retry interval, unless it's already overdue.
//
if (dwTimeElapsed < dwNATHelpRetryTime)
{
dwNATHelpRetryTime -= dwTimeElapsed;
}
else
{
dwNATHelpRetryTime = 0; // shortest time possible
}
}
//
// Modify the next time when we should refresh the NAT Help information based
// on the reported recommendation.
//
if (pdwTimerInterval != NULL)
{
DPFX(DPFPREP, 6, "Modifying NAT Help refresh timer for thread pool 0x%p in place (was %u ms, changing to %u).",
this, (*pdwTimerInterval), dwNATHelpRetryTime);
(*pdwTimerInterval) = dwNATHelpRetryTime;
}
else
{
//
// Add the interval to the current time to find the new retry time.
//
dwCurrentTime += dwNATHelpRetryTime;
DPFX(DPFPREP, 6, "Modifying NAT Help refresh timer for thread pool 0x%p to run at offset %u (in %u ms).",
this, dwCurrentTime, dwNATHelpRetryTime);
//
// Try to modify the existing timer job. There are two race conditions:
// 1) the recurring timer that triggered this refresh (which occurs on a
// separate non-blockable thread) has not rescheduled itself yet. It
// should pick up the new timer setting we're about to apply when it
// eventually does reschedule. ModifyTimerJobNextRetryTime should
// succeed.
// 2) the NAT help timer is being killed. ModifyTimerJobNextRetryTime
// will fail, but there's nothing we can or should do.
//
fModifiedRetryInterval = ModifyTimerJobNextRetryTime(this, dwCurrentTime);
if (! fModifiedRetryInterval)
{
DPFX(DPFPREP, 1, "Unable to modify NAT Help refresh timer (thread pool 0x%p), timer should be cancelled.",
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();
}
//**********************************************************************
#endif // DPNBUILD_NONATHELP
#ifndef DPNBUILD_NOSPUI
//**********************************************************************
// ------------------------------
// CThreadPool::DialogThreadProc - thread proc for spawning dialogs
//
// Entry: Pointer to startup parameter
//
// Exit: Error Code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::DialogThreadProc"
DWORD WINAPI CThreadPool::DialogThreadProc( void *pParam )
{
const DIALOG_THREAD_PARAM *pThreadParam;
BOOL fComInitialized;
//
// Initialize COM. If this fails, we'll have problems later.
//
fComInitialized = FALSE;
switch ( COM_CoInitialize( NULL ) )
{
case S_OK:
{
fComInitialized = TRUE;
break;
}
case S_FALSE:
{
DNASSERT( FALSE );
fComInitialized = TRUE;
break;
}
//
// COM init failed!
//
default:
{
DPFX(DPFPREP, 0, "Failed to initialize COM!" );
DNASSERT( FALSE );
break;
}
}
DNASSERT( pParam != NULL );
pThreadParam = static_cast<DIALOG_THREAD_PARAM*>( pParam );
pThreadParam->pDialogFunction( pThreadParam->pContext );
DNFree( pParam );
if ( fComInitialized != FALSE )
{
COM_CoUninitialize();
fComInitialized = FALSE;
}
return 0;
}
//**********************************************************************
#endif // ! DPNBUILD_NOSPUI
#ifndef DPNBUILD_ONLYWINSOCK2
//**********************************************************************
// ------------------------------
// 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 )
{
#ifdef DPNBUILD_NONEWTHREADPOOL
static const TIMEVAL SelectTime = { 0, 0 }; // zero, do an instant check
#else // ! DPNBUILD_NONEWTHREADPOOL
TIMEVAL SelectTime = { 0, (g_dwSelectTimeSlice * 1000)}; // convert ms into microseconds
#endif // ! DPNBUILD_NONEWTHREADPOOL
BOOL fIOServiced;
INT iSelectReturn;
FD_SET ReadSocketSet;
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();
if (m_fCancelWinsock1IO)
{
DPFX(DPFPREP, 1, "Detected Winsock 1 I/O cancellation, aborting.");
Unlock();
return FALSE;
}
memcpy( &ReadSocketSet, pWinsock1Sockets, sizeof( ReadSocketSet ) );
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 = select( 0, // compatibility parameter (ignored)
&ReadSocketSet, // sockets to check for read
NULL, // sockets to check for write (none)
&ErrorSocketSet, // sockets to check for error
&SelectTime // wait timeout
);
switch ( iSelectReturn )
{
//
// timeout
//
case 0:
{
break;
}
//
// select got pissed
//
case SOCKET_ERROR:
{
DWORD dwWSAError;
dwWSAError = 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;
}
}
return fIOServiced;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// 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 ( __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 // ! DPNBUILD_ONLYWINSOCK2
//**********************************************************************
// ------------------------------
// CThreadPool::SubmitDelayedCommand - submit request to perform work in another thread
//
// Entry: CPU index (non DPNBUILD_ONLYONEPROCESSOR builds only)
// Pointer to callback function
// Pointer to callback context
//
// Exit: Error code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::SubmitDelayedCommand"
#ifdef DPNBUILD_ONLYONEPROCESSOR
HRESULT CThreadPool::SubmitDelayedCommand( const PFNDPTNWORKCALLBACK pFunction,
void *const pContext )
{
return IDirectPlay8ThreadPoolWork_QueueWorkItem(m_pDPThreadPoolWork,
-1,
pFunction,
pContext,
0);
}
#else // ! DPNBUILD_ONLYONEPROCESSOR
HRESULT CThreadPool::SubmitDelayedCommand( const DWORD dwCPU,
const PFNDPTNWORKCALLBACK pFunction,
void *const pContext )
{
return IDirectPlay8ThreadPoolWork_QueueWorkItem(m_pDPThreadPoolWork,
dwCPU,
pFunction,
pContext,
0);
}
#endif // ! DPNBUILD_ONLYONEPROCESSOR
//**********************************************************************
//**********************************************************************
// ------------------------------
// CThreadPool::GenericTimerCallback - generic timer callback
//
// Entry: Pointer to callback context
// Pointer to timer data
// Pointer to timer uniqueness value
//
// Exit: None
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::GenericTimerCallback"
void WINAPI CThreadPool::GenericTimerCallback( void * const pvContext,
void * const pvTimerData,
const UINT uiTimerUnique )
{
TIMER_OPERATION_ENTRY * pTimerEntry;
CThreadPool * pThisThreadPool;
DWORD dwCurrentTime;
HRESULT hr;
DWORD dwNewTimerDelay;
#ifdef DBG
DWORD dwNextRetryTime;
#endif // DBG
pTimerEntry = (TIMER_OPERATION_ENTRY*) pvContext;
DNASSERT((pvTimerData == pTimerEntry->pvTimerData) || (pTimerEntry->pvTimerData == NULL));
DNASSERT((uiTimerUnique == pTimerEntry->uiTimerUnique) || (pTimerEntry->uiTimerUnique == 0));
pThisThreadPool = pTimerEntry->pThreadPool;
//
// Process the timer, unless we just went through the idle timeout.
//
if (pTimerEntry->uRetryCount != 0)
{
dwCurrentTime = GETTIMESTAMP();
#ifdef DBG
dwNextRetryTime = pTimerEntry->dwNextRetryTime; // copy since lock is not held
if ((int) (dwNextRetryTime - dwCurrentTime) <= 0)
{
//
// Timer expired and has not been rescheduled yet.
//
DPFX(DPFPREP, 7, "Threadpool 0x%p performing timed job 0x%p approximately %u ms after intended time of %u.",
pThisThreadPool, pTimerEntry, (dwCurrentTime - dwNextRetryTime), pTimerEntry->dwNextRetryTime);
}
else
{
//
// Another thread may have modified the timer already.
//
DPFX(DPFPREP, 7, "Threadpool 0x%p performing timed job 0x%p (next time already modified to be %u).",
pThisThreadPool, pTimerEntry, dwNextRetryTime);
}
#endif // DBG
//
// Execute this timed item.
//
pTimerEntry->pTimerCallback(pTimerEntry->pContext);
//
// Reschedule the job, unless it was *just* cancelled, or it's idling
// forever.
//
pThisThreadPool->LockTimerData();
if (pTimerEntry->fCancelling)
{
DPFX(DPFPREP, 5, "Timer 0x%p was just cancelled, completing.", pTimerEntry);
pTimerEntry->Linkage.RemoveFromList();
pThisThreadPool->UnlockTimerData();
pTimerEntry->pTimerComplete(DPNERR_USERCANCEL, pTimerEntry->pContext);
g_TimerEntryPool.Release(pTimerEntry);
}
else
{
//
// If this job isn't running forever, decrement the retry count.
// If there are no more retries, set up the idle timer.
//
if ( pTimerEntry->fRetryForever == FALSE )
{
pTimerEntry->uRetryCount--;
if ( pTimerEntry->uRetryCount == 0 )
{
if ( pTimerEntry->fIdleWaitForever == FALSE )
{
//
// Compute stopping time for this job's 'Timeout' phase.
//
dwNewTimerDelay = pTimerEntry->dwIdleTimeout;
pTimerEntry->dwIdleTimeout += dwCurrentTime;
}
else
{
//
// We're waiting forever for enum returns. ASSERT that we
// have the maximum timeout.
//
DNASSERT( pTimerEntry->dwIdleTimeout == -1 );
//
// Set this value to avoid a false PREfast warning.
//
dwNewTimerDelay = 0;
}
goto SkipNextRetryTimeComputation;
}
} // end if (don't retry forever)
dwNewTimerDelay = pTimerEntry->dwRetryInterval;
pTimerEntry->dwNextRetryTime = dwCurrentTime + pTimerEntry->dwRetryInterval;
SkipNextRetryTimeComputation:
if ((! pTimerEntry->fIdleWaitForever) ||
(pTimerEntry->uRetryCount > 0))
{
//
// Make sure we aren't trying to schedule something too far in
// the future or backward in time. If we are, we'll just force
// the timer to expire earlier.
//
if ((int) dwNewTimerDelay < 0)
{
DNASSERT(! "Job time is unexpectedly long or backward in time!");
dwNewTimerDelay = 0x7FFFFFFF;
}
hr = IDirectPlay8ThreadPoolWork_ResetCompletingTimer(pThisThreadPool->m_pDPThreadPoolWork,
pvTimerData, // timer data
dwNewTimerDelay, // delay
CThreadPool::GenericTimerCallback, // callback
pTimerEntry, // user context
&pTimerEntry->uiTimerUnique, // new timer uniqueness value
0); // flags
DNASSERT(hr == DPN_OK);
pTimerEntry->pvTimerData = pvTimerData; // ensure that we remember the timer handle
pThisThreadPool->UnlockTimerData();
}
else
{
DPFX(DPFPREP, 5, "Timer 0x%p now idling forever.", pTimerEntry);
pTimerEntry->pvTimerData = NULL;
pThisThreadPool->UnlockTimerData();
}
}
}
else
{
DNASSERT((int) (pTimerEntry->dwIdleTimeout - GETTIMESTAMP()) <= 0);
//
// Remove this link from the list, tell owner that the job is
// complete and return the job to the pool.
//
pThisThreadPool->LockTimerData();
pTimerEntry->Linkage.RemoveFromList();
pThisThreadPool->UnlockTimerData();
pTimerEntry->pTimerComplete(DPN_OK, pTimerEntry->pContext);
g_TimerEntryPool.Release(pTimerEntry);
}
}
//**********************************************************************
#ifndef DPNBUILD_ONLYWINSOCK2
//**********************************************************************
// ------------------------------
// CThreadPool::CheckWinsock1IOCallback - Winsock1 I/O servicing callback
//
// Pointer to timer data
// Pointer to timer uniqueness value
// Entry: Pointer to callback context
//
// Exit: None
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "CThreadPool::CheckWinsock1IOCallback"
void WINAPI CThreadPool::CheckWinsock1IOCallback( void * const pvContext,
void * const pvTimerData,
const UINT uiTimerUnique )
{
CThreadPool * pThisThreadPool = (CThreadPool*) pvContext;
BOOL fResult;
HRESULT hr;
//
// Service all Winsock1 I/O possible.
//
do
{
fResult = pThisThreadPool->CheckWinsock1IO(&pThisThreadPool->m_SocketSet);
}
while (fResult);
//
// Schedule the timer again, unless we're cancelling it.
//
pThisThreadPool->Lock();
DNASSERT(pvTimerData == pThisThreadPool->m_pvTimerDataWinsock1IO);
DNASSERT(uiTimerUnique == pThisThreadPool->m_uiTimerUniqueWinsock1IO);
if (! pThisThreadPool->m_fCancelWinsock1IO)
{
hr = IDirectPlay8ThreadPoolWork_ResetCompletingTimer(pThisThreadPool->m_pDPThreadPoolWork,
pvTimerData, // timer data
g_dwSelectTimePeriod, // delay
CThreadPool::CheckWinsock1IOCallback, // callback
pThisThreadPool, // user context
&pThisThreadPool->m_uiTimerUniqueWinsock1IO, // updated timer uniqueness value
0); // flags
DNASSERT(hr == DPN_OK);
}
else
{
DPFX(DPFPREP, 1, "Not resubmitting Winsock1 I/O timer due to cancellation.");
pThisThreadPool->m_fCancelWinsock1IO = FALSE;
pThisThreadPool->m_pvTimerDataWinsock1IO = NULL;
}
pThisThreadPool->Unlock();
}
//**********************************************************************
#endif // ! DPNBUILD_ONLYWINSOCK2
#ifndef DPNBUILD_ONLYONETHREAD
//**********************************************************************
// ------------------------------
// DPNBlockingJobThreadProc - thread procedure for executing blocking jobs
//
// Entry: Parameter
//
// Exit: Result code
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "DPNBlockingJobThreadProc"
DWORD WINAPI DPNBlockingJobThreadProc(PVOID pvParameter)
{
HRESULT hr;
CThreadPool * pThisThreadPool;
BOOL fUninitializeCOM = TRUE;
DPFX(DPFPREP, 5, "Parameters: (0x%p)", pvParameter);
pThisThreadPool = (CThreadPool*) pvParameter;
//
// Init COM.
//
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hr))
{
DPFX(DPFPREP, 0, "Failed to initialize COM (err = 0x%lx)! Continuing.", hr);
fUninitializeCOM = FALSE;
//
// Continue...
//
}
//
// Process all jobs until we're told to quit.
//
pThisThreadPool->DoBlockingJobs();
if (fUninitializeCOM)
{
CoUninitialize();
fUninitializeCOM = FALSE;
}
DPFX(DPFPREP, 5, "Leave");
return 0;
}
//**********************************************************************
#endif // ! DPNBUILD_ONLYONETHREAD
//**********************************************************************
// ------------------------------
// 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 "TimerEntry_Alloc"
BOOL TimerEntry_Alloc( void *pvItem, void* pvContext )
{
TIMER_OPERATION_ENTRY* pTimerEntry = (TIMER_OPERATION_ENTRY*)pvItem;
DNASSERT( pvItem != NULL );
DEBUG_ONLY( memset( pTimerEntry, 0x00, sizeof( *pTimerEntry ) ) );
pTimerEntry->Linkage.Initialize();
return TRUE;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// TimerEntry_Get - get new timer job entry from pool
//
// Entry: Pointer to new entry
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "TimerEntry_Get"
void TimerEntry_Get( void *pvItem, void* pvContext )
{
#ifdef DBG
const TIMER_OPERATION_ENTRY* pTimerEntry = (TIMER_OPERATION_ENTRY*)pvItem;
DNASSERT( pvItem != NULL );
DNASSERT( pTimerEntry->pContext == NULL );
DNASSERT( pTimerEntry->Linkage.IsEmpty() != FALSE );
#endif // DBG
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// TimerEntry_Release - return timer job entry to pool
//
// Entry: Pointer to entry
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "TimerEntry_Release"
void TimerEntry_Release( void *pvItem )
{
TIMER_OPERATION_ENTRY* pTimerEntry = (TIMER_OPERATION_ENTRY*)pvItem;
DNASSERT( pvItem != NULL );
DNASSERT( pTimerEntry->Linkage.IsEmpty() != FALSE );
pTimerEntry->pContext= NULL;
}
//**********************************************************************
//**********************************************************************
// ------------------------------
// TimerEntry_Dealloc - deallocate a timer job entry
//
// Entry: Pointer to entry
//
// Exit: Nothing
// ------------------------------
#undef DPF_MODNAME
#define DPF_MODNAME "TimerEntry_Dealloc"
void TimerEntry_Dealloc( void *pvItem )
{
#ifdef DBG
const TIMER_OPERATION_ENTRY* pTimerEntry = (TIMER_OPERATION_ENTRY*)pvItem;
DNASSERT( pvItem != NULL );
DNASSERT( pTimerEntry->Linkage.IsEmpty() != FALSE );
DNASSERT( pTimerEntry->pContext == NULL );
#endif // DBG
}
//**********************************************************************