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.
 
 
 
 
 
 

2910 lines
77 KiB

/*++
Copyright (c) 1995 Microsoft Corporation
Module Name :
atqmain.cxx
Abstract:
This module implements entry points for ATQ - Asynchronous Thread Queue.
Author:
Murali R. Krishnan ( MuraliK ) 8-Apr-1996
Environment:
User Mode -- Win32
Project:
Internet Services Common DLL
Functions Exported:
BOOL AtqInitialize();
BOOL AtqTerminate();
BOOL AtqGetCompletionPort();
DWORD AtqSetInfo();
DWORD AtqGetInfo();
BOOL AtqGetStatistics();
BOOL AtqClearStatistics();
BOOL AtqAddAcceptExSockets();
BOOL AtqAddAsyncHandle();
DWORD AtqContextSetInfo();
VOID AtqCloseSocket();
VOID AtqFreeContext();
BOOL AtqReadFile();
BOOL AtqWriteFile();
BOOL AtqTransmitFile();
BOOL AtqPostCompletionStatus();
PVOID AtqAllocateBandwidthInfo();
BOOL AtqFreeBandwidthInfo();
DWORD AtqBandwidthSetInfo();
--*/
#include "isatq.hxx"
#include <iscaptrc.h>
# define ATQ_REG_DEF_THREAD_TIMEOUT_PWS (30*60) // 30 minutes
/************************************************************
* Globals
************************************************************/
//
// specifies the registry location to use for getting the ATQ Configuration
// (Global overrides)
//
CHAR g_PSZ_ATQ_CONFIG_PARAMS_REG_KEY[] =
TEXT("System\\CurrentControlSet\\Services\\InetInfo\\Parameters");
// ----------------------------------------
// # of CPUs in machine (for thread-tuning)
// ----------------------------------------
DWORD g_cCPU = 0;
//
// concurrent # of threads to run per processor
//
DWORD g_cConcurrency = ATQ_REG_DEF_PER_PROCESSOR_CONCURRENCY;
//
// Amount of time (in ms) a worker thread will be idle before suicide
//
DWORD g_msThreadTimeout = ATQ_REG_DEF_THREAD_TIMEOUT * 1000;
BOOL g_fUseAcceptEx = TRUE; // Use AcceptEx if available
//
// The absolute thread limit
//
LONG g_cMaxThreadLimit = ATQ_REG_DEF_POOL_THREAD_LIMIT;
//
// Assumed minimum file transfer rate
//
DWORD g_cbMinKbSec = ATQ_REG_DEF_MIN_KB_SEC;
//
// number of active context list
//
DWORD g_dwNumContextLists = ATQ_NUM_CONTEXT_LIST;
/*
g_pfnExitThreadCallback()
This routine sets the callback routine to be called when one of the
Atq threads exit so that thread state data can be cleaned up. Currently
support is for a single routine. One way to support multiple routines would
be for the caller to save the return value. Such an application would not
be able to delete the "saved" callback routine.
*/
ATQ_THREAD_EXIT_CALLBACK g_pfnExitThreadCallback = NULL;
//
// mswsock entry points
//
HINSTANCE g_hMSWsock = NULL;
PFN_ACCEPTEX g_pfnAcceptEx = NULL;
PFN_GETACCEPTEXSOCKADDRS g_pfnGetAcceptExSockaddrs = NULL;
PFN_TRANSMITFILE g_pfnTransmitFile = NULL;
PFN_GET_QUEUED_COMPLETION_STATUS g_pfnGetQueuedCompletionStatus = NULL;
PFN_CREATE_COMPLETION_PORT g_pfnCreateCompletionPort = NULL;
PFN_CLOSE_COMPLETION_PORT g_pfnCloseCompletionPort = NULL;
PFN_POST_COMPLETION_STATUS g_pfnPostCompletionStatus = NULL;
//
// NT specific
//
PFN_READ_DIR_CHANGES_W g_pfnReadDirChangesW = NULL;
// ------------------------------
// Current State Information
// ------------------------------
HANDLE g_hCompPort = NULL; // Handle for completion port
LONG g_cThreads = 0; // number of thread in the pool
LONG g_cAvailableThreads = 0; // # of threads waiting on the port.
//
// Should we use the TF_USE_KERNEL_APC flag for TransmitFile
//
BOOL g_fUseKernelApc = ATQ_REG_DEF_USE_KERNEL_APC;
//
// Current thread limit
//
LONG g_cMaxThreads = ATQ_REG_DEF_PER_PROCESSOR_ATQ_THREADS;
DWORD g_cListenBacklog = ATQ_REG_DEF_LISTEN_BACKLOG;
BOOL g_fShutdown = FALSE; // if set, indicates that we are shutting down
// in that case, all threads should exit.
HANDLE g_hShutdownEvent = NULL; // set when all running threads shutdown
BOOL g_fEnableDebugThreads = FALSE; // if TRUE, debug IO threads can be
// created
BOOL g_fCreateDebugThread = FALSE; // set to TRUE to create a debug thread
// ------------------------------
// Bandwidth Throttling Info
// ------------------------------
PBANDWIDTH_INFO g_pBandwidthInfo = NULL;
// ------------------------------
// Various State/Object Lists
// ------------------------------
//
// Used to switch context between lists
//
DWORD AtqGlobalContextCount = 0;
//
// List of active context
//
ATQ_CONTEXT_LISTHEAD AtqActiveContextList[ATQ_NUM_CONTEXT_LIST];
//
// List of Endpoints in ATQ - one per listen socket
//
LIST_ENTRY AtqEndpointList;
CRITICAL_SECTION AtqEndpointLock;
PALLOC_CACHE_HANDLER g_pachAtqContexts;
#ifdef IIS_AUX_COUNTERS
LONG g_AuxCounters[NUM_AUX_COUNTERS];
#endif // IIS_AUX_COUNTERS
//
// Timeout before closing pending listens in case backlog is full
//
DWORD g_cForceTimeout;
//
// Flag enabling/disabling the backlog monitor
//
BOOL g_fDisableBacklogMonitor = FALSE;
// ------------------------------
// local to this module
// ------------------------------
LONG sg_AtqInitializeCount = -1;
HANDLE g_hCapThread = NULL;
DWORD
I_AtqGetGlobalConfiguration(VOID);
DWORD
I_NumAtqEndpointsOpen(VOID);
DWORD
AtqDebugCreatorThread(
LPDWORD param
);
//
// Capacity Planning variables
//
extern TRACEHANDLE IISCapTraceRegistrationHandle;
//
// Ensure that initialization/termination don't happen at the same time
//
CRITICAL_SECTION g_csInitTermLock;
/************************************************************
* Functions
************************************************************/
BOOL
AtqInitialize(
IN DWORD dwFlags
)
/*++
Routine Description:
Initializes the ATQ package
Arguments:
dwFlags - DWORD containing the flags for use to initialize ATQ library.
This dword helps to shut off the unwanted flags.
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
Note:
As of 4/16/97 the pszRegKey that is sent is no more utilized.
We always load the internal configuration parameters from
one single registry entry specified by PSZ_ATQ_CONFIG_PARAMS_REG_KEY
The parameter is left in the command line for compatibility
with old callers :( - NYI: Need to change this.
--*/
{
DWORD i;
DWORD dwThreadID;
DBGPRINTF(( DBG_CONTEXT, "AtqInitialize, %d, %x\n",
sg_AtqInitializeCount, dwFlags));
if ( InterlockedIncrement( &sg_AtqInitializeCount) != 0) {
IF_DEBUG( API_ENTRY) {
ATQ_PRINTF(( DBG_CONTEXT,
"AtqInitialize( %08x). ATQ is already initialized.\n",
dwFlags));
}
//
// we are already initialized. Ignore the new registry settings
//
return ( TRUE);
}
EnterCriticalSection( &g_csInitTermLock );
IF_DEBUG( API_ENTRY) {
ATQ_PRINTF(( DBG_CONTEXT,
"AtqInitialize[%08x]. Initializing....\n",
dwFlags));
}
// get the number of processors for this machine
// do it only for NT Server only (don't scale workstation)
if ( TsIsNtServer() ) {
SYSTEM_INFO si;
GetSystemInfo( &si );
g_cCPU = si.dwNumberOfProcessors;
} else {
g_cCPU = 1;
}
//
// Initialize context lists and crit sects
//
ATQ_CONTEXT_LISTHEAD * pacl;
for ( pacl = AtqActiveContextList;
pacl < (AtqActiveContextList + g_dwNumContextLists);
pacl++) {
pacl->Initialize();
}
InitializeListHead( &AtqEndpointList );
//
// init bandwidth throttling
//
ATQ_REQUIRE( BANDWIDTH_INFO::AbwInitialize() );
//
// Read registry configurable Atq options. We have to read these now
// because concurrency is set for the completion port at creation time.
//
DWORD dwError = I_AtqGetGlobalConfiguration();
if ( NO_ERROR != dwError) {
SetLastError( dwError );
goto cleanup;
}
//
// Setup an allocation cache for the ATQ Contexts
// NYI: Auto-tune the threshold limit
//
{
ALLOC_CACHE_CONFIGURATION acConfig;
DWORD nCachedAtq = ATQ_CACHE_LIMIT_NTS;
acConfig.nConcurrency = 1;
acConfig.nThreshold = nCachedAtq;
acConfig.cbSize = sizeof(ATQ_CONTEXT);
g_pachAtqContexts = new ALLOC_CACHE_HANDLER( "ATQ", &acConfig);
if ( NULL == g_pachAtqContexts) {
goto cleanup;
}
}
//
// Create the shutdown event
//
g_hShutdownEvent = IIS_CREATE_EVENT(
"g_hShutdownEvent",
&g_hShutdownEvent,
TRUE, // Manual reset
FALSE // Not signalled
);
if ( !g_hShutdownEvent ) {
DBGERROR(( DBG_CONTEXT, "Create Shutdown event failed. Last Error = 0x%x\n",
GetLastError()
));
goto cleanup;
}
//
// Create the completion port
//
g_hCompPort = g_pfnCreateCompletionPort(INVALID_HANDLE_VALUE,
NULL,
0,
g_cConcurrency
);
if ( !g_hCompPort ) {
DBGERROR(( DBG_CONTEXT, "Create IoComp port failed. Last Error = 0x%x\n",
GetLastError()
));
goto cleanup;
}
//
// Initialize Backlog Monitor
//
if ( !g_fDisableBacklogMonitor )
{
DBG_ASSERT( g_pAtqBacklogMonitor == NULL );
g_pAtqBacklogMonitor = new ATQ_BACKLOG_MONITOR;
if (!g_pAtqBacklogMonitor) {
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
goto cleanup;
}
}
//
// Ensure all other initializations also are done
//
g_cThreads = 0;
g_fShutdown = FALSE;
g_cAvailableThreads = 0;
if ( !I_AtqStartTimeoutProcessing( NULL ) ) {
goto cleanup;
}
IF_DEBUG(INIT_CLEAN) {
DBGPRINTF(( DBG_CONTEXT,
"fUseAcceptEx[%d] NT CompPort[1] Platform[%d]\n",
g_fUseAcceptEx,
IISPlatformType()
));
}
//
// Create the initial ATQ thread.
//
(VOID)I_AtqCheckThreadStatus( (PVOID)ATQ_INITIAL_THREAD );
//
// Create a second thread if we are NTS
//
if ( TsIsNtServer() ) {
(VOID)I_AtqCheckThreadStatus( (PVOID)ATQ_INITIAL_THREAD );
}
//
// Initialize Capacity Planning Trace
//
// Spawn another thread to do this since IISInitializeCapTrace() can
// take a while and we are not in a state where SCM is getting
// SERVICE_STARTING messages
//
g_hCapThread = CreateThread( NULL,
0,
IISInitializeCapTrace,
NULL,
0,
&dwThreadID
);
IF_DEBUG( API_EXIT) {
ATQ_PRINTF(( DBG_CONTEXT,
"AtqInitialize( %08x) returns %d.\n",
dwFlags, TRUE));
}
//
// Create the debug thread starter if necessary
//
if ( g_fEnableDebugThreads )
{
DWORD dwError;
DWORD dwThreadID;
HANDLE hThread;
hThread = CreateThread( NULL,
0,
(LPTHREAD_START_ROUTINE)AtqDebugCreatorThread,
NULL,
0,
&dwThreadID );
if ( !hThread )
{
goto cleanup;
}
CloseHandle( hThread );
}
LeaveCriticalSection( &g_csInitTermLock );
return TRUE;
cleanup:
DWORD dwSaveError = GetLastError();
for (i=0; i<g_dwNumContextLists; i++) {
AtqActiveContextList[i].Cleanup();
}
if ( g_hShutdownEvent != NULL ) {
CloseHandle( g_hShutdownEvent );
g_hShutdownEvent = NULL;
}
if ( g_hCompPort != NULL ) {
g_pfnCloseCompletionPort( g_hCompPort );
g_hCompPort = NULL;
}
if ( NULL != g_pachAtqContexts) {
delete g_pachAtqContexts;
g_pachAtqContexts = NULL;
}
if ( NULL != g_pAtqBacklogMonitor ) {
delete g_pAtqBacklogMonitor;
g_pAtqBacklogMonitor = NULL;
}
ATQ_REQUIRE( BANDWIDTH_INFO::AbwTerminate());
IF_DEBUG( API_EXIT) {
ATQ_PRINTF(( DBG_CONTEXT,
"AtqInitialize( %08x) returns %d.\n",
dwFlags, FALSE));
}
sg_AtqInitializeCount = -1;
SetLastError(dwSaveError);
LeaveCriticalSection( &g_csInitTermLock );
return(FALSE);
} // AtqInitialize()
BOOL
AtqTerminate(
VOID
)
/*++
Routine Description:
Cleans up the ATQ package. Should only be called after all of the
clients of ATQ have been shutdown.
Arguments:
None.
Return Value:
TRUE, if ATQ was shutdown properly
FALSE, otherwise
--*/
{
DBGPRINTF(( DBG_CONTEXT, "AtqTerminate, %d\n", sg_AtqInitializeCount));
DWORD currentThreadCount;
ATQ_CONTEXT_LISTHEAD * pacl;
BOOL fRet = TRUE;
DWORD dwErr;
// there are outstanding users, don't fully terminate
if ( InterlockedDecrement( &sg_AtqInitializeCount) >= 0) {
/*IF_DEBUG( API_ENTRY)*/ {
ATQ_PRINTF(( DBG_CONTEXT,
"AtqTerminate() - there are other users."
" Not terminating now\n"
));
}
return (TRUE);
}
EnterCriticalSection( &g_csInitTermLock );
/*IF_DEBUG( API_ENTRY)*/ {
ATQ_PRINTF(( DBG_CONTEXT,
"AtqTerminate() - Terminating ATQ ...\n"
));
}
//
// All the ATQ endpoints should have been terminated before calling
// this ATQTerminate() function. If not, sorry return failure.
//
DWORD nEndpointsToBeClosed = I_NumAtqEndpointsOpen();
if ( nEndpointsToBeClosed > 0) {
DBGPRINTF(( DBG_CONTEXT,
" There are %d endpoints remaining to be closed."
" Somebody above stream did not close endpoints."
" BUG IN CODE ABOVE ATQ\n"
,
nEndpointsToBeClosed
));
SetLastError( ERROR_NETWORK_BUSY);
fRet = FALSE;
goto Finished;
}
if ( (g_hShutdownEvent == NULL) || g_fShutdown ) {
//
// We have not been intialized or have already terminated.
//
SetLastError( ERROR_NOT_READY );
fRet = FALSE;
goto Finished;
}
//
// All clients should have cleaned themselves up before calling us.
//
for ( pacl = AtqActiveContextList;
pacl < (AtqActiveContextList + g_dwNumContextLists);
pacl++) {
pacl->Lock();
if ( !IsListEmpty(&pacl->ActiveListHead)) {
ATQ_ASSERT( IsListEmpty( &pacl->ActiveListHead));
pacl->Unlock();
IF_DEBUG( API_EXIT) {
ATQ_PRINTF(( DBG_CONTEXT,
"AtqTerminate() - ContextList(%08x) has "
"Active Contexts. Failed Termination.\n",
pacl
));
}
fRet = FALSE;
goto Finished;
}
pacl->Unlock();
} // for
//
// Note that we are shutting down and prevent any more handles from
// being added to the completion port.
//
g_fShutdown = TRUE;
//
// Attempt and remove the TimeOut Context from scheduler queue
//
DBG_REQUIRE( I_AtqStopTimeoutProcessing());
currentThreadCount = g_cThreads;
if (currentThreadCount > 0) {
DWORD i;
BOOL fRes;
OVERLAPPED overlapped;
//
// Post a message to the completion port for each worker thread
// telling it to exit. The indicator is a NULL context in the
// completion.
//
ZeroMemory( &overlapped, sizeof(OVERLAPPED) );
for (i=0; i<currentThreadCount; i++) {
fRes = g_pfnPostCompletionStatus( g_hCompPort,
0,
0,
&overlapped );
ATQ_ASSERT( (fRes == TRUE) ||
( (fRes == FALSE) &&
(GetLastError() == ERROR_IO_PENDING) )
);
}
}
//
// Now wait for the pool threads to shutdown.
//
dwErr = WaitForSingleObject( g_hShutdownEvent, ATQ_WAIT_FOR_THREAD_DEATH);
#if 0
DWORD dwWaitCount = 0;
while ( dwErr == WAIT_TIMEOUT) {
dwWaitCount++;
DebugBreak();
Sleep( 10*1000); // sleep for some time
dwErr =
WaitForSingleObject( g_hShutdownEvent, ATQ_WAIT_FOR_THREAD_DEATH);
} // while
# endif // 0
//
// At this point, no other threads should be left running.
//
//
// g_cThreads counter is decremented by AtqPoolThread().
// AtqTerminate() is called during the DLL termination
// But at DLL termination, all ATQ pool threads are killed =>
// no one is decrementing the count. Hence this assert will always fail.
//
// ATQ_ASSERT( !g_cThreads );
ATQ_REQUIRE( CloseHandle( g_hShutdownEvent ) );
g_pfnCloseCompletionPort( g_hCompPort );
g_hShutdownEvent = NULL;
g_hCompPort = NULL;
//
// Cleanup our synchronization resources
//
for ( pacl = AtqActiveContextList;
pacl < (AtqActiveContextList + g_dwNumContextLists);
pacl++) {
PLIST_ENTRY pEntry;
pacl->Lock();
if ( !IsListEmpty( &pacl->PendingAcceptExListHead)) {
for ( pEntry = pacl->PendingAcceptExListHead.Flink;
pEntry != &pacl->PendingAcceptExListHead;
pEntry = pEntry->Flink ) {
PATQ_CONT pContext =
CONTAINING_RECORD( pEntry, ATQ_CONTEXT, m_leTimeout );
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
pContext->Print();
} // for
}
pacl->Unlock();
pacl->Cleanup();
}
//
// Free all the elements in the Allocation caching list
//
if ( NULL != g_pachAtqContexts) {
delete g_pachAtqContexts;
g_pachAtqContexts = NULL;
}
if ( g_hCapThread )
{
WaitForSingleObject( g_hCapThread, INFINITE );
CloseHandle( g_hCapThread );
g_hCapThread = NULL;
if (IISCapTraceRegistrationHandle != (TRACEHANDLE)0)
{
UnregisterTraceGuids( IISCapTraceRegistrationHandle );
}
}
//
// cleanup backlog monitor
//
delete g_pAtqBacklogMonitor;
g_pAtqBacklogMonitor = NULL;
// Cleanup variables in ATQ Bandwidth throttle module
if ( !BANDWIDTH_INFO::AbwTerminate()) {
// there may be a few blocked IO. We should avoid them all.
// All clients should have cleaned themselves up before coming here.
fRet = FALSE;
goto Finished;
}
if ( g_hMSWsock != NULL ) {
FreeLibrary(g_hMSWsock);
g_hMSWsock = NULL;
}
IF_DEBUG( API_EXIT) {
ATQ_PRINTF(( DBG_CONTEXT,
"AtqTerminate() - Successfully cleaned up.\n"
));
}
Finished:
LeaveCriticalSection( &g_csInitTermLock );
return fRet;
} // AtqTerminate()
HANDLE
AtqGetCompletionPort()
/*++
Routine Description:
Return the completion port created by ATQ
Arguments:
Return Value:
Handle to ATQ completion port
--*/
{
return g_hCompPort;
} // AtqGetCompletionPort()
ULONG_PTR
AtqSetInfo(
IN ATQ_INFO atqInfo,
IN ULONG_PTR Data
)
/*++
Routine Description:
Sets various bits of information for the ATQ module
Arguments:
atqInfo - Data item to set
data - New value for item
Return Value:
The old value of the parameter
--*/
{
ULONG_PTR oldVal = 0;
switch ( atqInfo ) {
case AtqBandwidthThrottle:
ATQ_ASSERT( g_pBandwidthInfo != NULL );
oldVal = (ULONG_PTR)g_pBandwidthInfo->SetBandwidthLevel( (DWORD)Data );
break;
case AtqBandwidthThrottleMaxBlocked:
ATQ_ASSERT( g_pBandwidthInfo != NULL );
oldVal = (ULONG_PTR)g_pBandwidthInfo->SetMaxBlockedListSize( (DWORD)Data );
break;
case AtqExitThreadCallback:
oldVal = (ULONG_PTR)g_pfnExitThreadCallback;
g_pfnExitThreadCallback = (ATQ_THREAD_EXIT_CALLBACK ) Data;
break;
case AtqMaxPoolThreads:
// the value is per processor values
// internally we maintain value for all processors
oldVal = (ULONG_PTR)( g_cMaxThreads/g_cCPU );
g_cMaxThreads = (DWORD)Data * g_cCPU;
break;
//
// Increment or decrement the max thread count. In this instance, we
// do not scale by the number of CPUs
//
case AtqIncMaxPoolThreads:
InterlockedIncrement( (LONG *) &g_cMaxThreads );
oldVal = TRUE;
break;
case AtqDecMaxPoolThreads:
InterlockedDecrement( (LONG *) &g_cMaxThreads );
oldVal = TRUE;
break;
case AtqMaxConcurrency:
oldVal = (ULONG_PTR)g_cConcurrency;
g_cConcurrency = (DWORD)Data;
break;
case AtqThreadTimeout:
oldVal = (ULONG_PTR)(g_msThreadTimeout/1000); // convert back to seconds
g_msThreadTimeout = (DWORD)Data * 1000; // convert value to millisecs
break;
case AtqUseAcceptEx:
oldVal = (ULONG_PTR)g_fUseAcceptEx;
g_fUseAcceptEx = (DWORD)Data;
break;
case AtqMinKbSec:
//
// Ignore it if the value is zero
//
if ( Data ) {
oldVal = (ULONG_PTR)g_cbMinKbSec;
g_cbMinKbSec = (DWORD)Data;
}
break;
default:
ATQ_ASSERT( FALSE );
break;
}
return oldVal;
} // AtqSetInfo()
ULONG_PTR
AtqGetInfo(
IN ATQ_INFO atqInfo
)
/*++
Routine Description:
Gets various bits of information for the ATQ module
Arguments:
atqInfo - Data item to set
Return Value:
The old value of the parameter
--*/
{
ULONG_PTR dwVal = 0;
switch ( atqInfo ) {
case AtqBandwidthThrottle:
ATQ_ASSERT( g_pBandwidthInfo != NULL );
dwVal = (ULONG_PTR ) g_pBandwidthInfo->QueryBandwidthLevel();
break;
case AtqExitThreadCallback:
dwVal = (ULONG_PTR ) g_pfnExitThreadCallback;
break;
case AtqMaxPoolThreads:
dwVal = (ULONG_PTR ) (g_cMaxThreads/g_cCPU);
break;
case AtqMaxConcurrency:
dwVal = (ULONG_PTR ) g_cConcurrency;
break;
case AtqThreadTimeout:
dwVal = (ULONG_PTR ) (g_msThreadTimeout/1000); // convert back to seconds
break;
case AtqUseAcceptEx:
dwVal = (ULONG_PTR ) g_fUseAcceptEx;
break;
case AtqMinKbSec:
dwVal = (ULONG_PTR ) g_cbMinKbSec;
break;
case AtqMaxThreadLimit:
dwVal = (ULONG_PTR ) g_cMaxThreadLimit;
break;
case AtqAvailableThreads:
dwVal = (ULONG_PTR) g_cAvailableThreads;
break;
default:
ATQ_ASSERT( FALSE );
break;
} // switch
return dwVal;
} // AtqGetInfo()
BOOL
AtqGetStatistics(IN OUT ATQ_STATISTICS * pAtqStats)
{
if ( pAtqStats != NULL) {
return g_pBandwidthInfo->GetStatistics( pAtqStats );
} else {
SetLastError( ERROR_INVALID_PARAMETER);
return (FALSE);
}
} // AtqGetStatistics()
BOOL
AtqClearStatistics( VOID)
{
return g_pBandwidthInfo->ClearStatistics();
} // AtqClearStatistics()
ULONG_PTR
AtqContextSetInfo(
PATQ_CONTEXT patqContext,
enum ATQ_CONTEXT_INFO atqInfo,
ULONG_PTR Data
)
/*++
Routine Description:
Sets various bits of information for this context
Arguments:
patqContext - pointer to ATQ context
atqInfo - Data item to set
data - New value for item
Return Value:
The old value of the parameter
--*/
{
PATQ_CONT pContext = (PATQ_CONT) patqContext;
ULONG_PTR OldVal = 0;
ATQ_ASSERT( pContext );
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
if ( pContext && pContext->Signature == ATQ_CONTEXT_SIGNATURE )
{
switch ( atqInfo ) {
case ATQ_INFO_TIMEOUT:
OldVal = (ULONG_PTR)pContext->TimeOut;
pContext->TimeOut = CanonTimeout( (DWORD)Data );
break;
case ATQ_INFO_RESUME_IO:
//
// set back the max timeout from pContext->TimeOut
// This will ensure that timeout processing can go on
// peacefully.
//
{
DWORD currentTime = AtqGetCurrentTick( );
DWORD timeout;
OldVal = (ULONG_PTR)pContext->NextTimeout;
timeout = pContext->TimeOut;
//
// Set the new timeout
//
I_SetNextTimeout(pContext);
//
// Return the old
//
if ( currentTime >= (DWORD)OldVal ) {
ATQ_ASSERT((OldVal & ATQ_INFINITE) == 0);
OldVal = 0;
} else if ( (OldVal & ATQ_INFINITE) == 0 ) {
OldVal -= currentTime;
}
// return correct units
OldVal = (ULONG_PTR)UndoCanonTimeout( (DWORD)OldVal );
}
break;
case ATQ_INFO_COMPLETION:
OldVal = (ULONG_PTR)pContext->pfnCompletion;
pContext->pfnCompletion = (ATQ_COMPLETION) Data;
break;
case ATQ_INFO_COMPLETION_CONTEXT:
ATQ_ASSERT( Data != 0 ); // NULL context not allowed
OldVal = (ULONG_PTR)pContext->ClientContext;
pContext->ClientContext = (void *) Data;
break;
case ATQ_INFO_BANDWIDTH_INFO:
{
ATQ_ASSERT( Data != 0 );
PBANDWIDTH_INFO pBandwidthInfo = (PBANDWIDTH_INFO) Data;
ATQ_ASSERT( pBandwidthInfo->QuerySignature() ==
ATQ_BW_INFO_SIGNATURE );
if ( !pBandwidthInfo->IsFreed() )
{
pContext->m_pBandwidthInfo = (PBANDWIDTH_INFO) Data;
pContext->m_pBandwidthInfo->Reference();
}
break;
}
case ATQ_INFO_ABORTIVE_CLOSE:
OldVal = (ULONG_PTR)pContext->IsFlag( ACF_ABORTIVE_CLOSE );
if ( Data )
{
pContext->SetFlag( ACF_ABORTIVE_CLOSE );
}
else
{
pContext->ResetFlag( ACF_ABORTIVE_CLOSE );
}
break;
case ATQ_INFO_FORCE_CLOSE:
OldVal = (ULONG_PTR)pContext->ForceClose();
pContext->SetForceClose( Data ? TRUE : FALSE );
break;
case ATQ_INFO_SET_OVL_OFFSET:
OldVal = 0;
pContext->Overlapped.Offset = ((LARGE_INTEGER*)Data)->LowPart;
pContext->Overlapped.OffsetHigh = ((LARGE_INTEGER*)Data)->HighPart;
break;
default:
ATQ_ASSERT( FALSE );
}
}
return OldVal;
} // AtqContextSetInfo()
BOOL
AtqAddAsyncHandle(
PATQ_CONTEXT * ppatqContext,
PVOID EndpointObject,
PVOID ClientContext,
ATQ_COMPLETION pfnCompletion,
DWORD TimeOut,
HANDLE hAsyncIO
)
/*++
Routine Description:
Adds a handle to the thread queue
The client should call this after the IO handle is opened
and before the first IO request is made
Even in the case of failure, client should call AtqFreeContext() and
free the memory associated with this object.
Arguments:
ppatqContext - Receives allocated ATQ Context
Context - Context to call client with
pfnCompletion - Completion to call when IO completes
TimeOut - Time to wait (sec) for IO completion (INFINITE is valid)
hAsyncIO - Handle with pending read or write
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
--*/
{
return ( I_AtqAddAsyncHandle( (PATQ_CONT *) ppatqContext,
(PATQ_ENDPOINT) EndpointObject,
ClientContext,
pfnCompletion,
TimeOut,
hAsyncIO)
&&
I_AddAtqContextToPort( *((PATQ_CONT *) ppatqContext))
);
} // AtqAddAsyncHandle()
VOID
AtqGetAcceptExAddrs(
IN PATQ_CONTEXT patqContext,
OUT SOCKET * pSock,
OUT PVOID * ppvBuff,
OUT PVOID * pEndpointContext,
OUT SOCKADDR * * ppsockaddrLocal,
OUT SOCKADDR * * ppsockaddrRemote
)
{
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
INT cbsockaddrLocal;
INT cbsockaddrRemote;
DWORD cb;
ATQ_ASSERT( g_fUseAcceptEx);
ATQ_ASSERT( pContext->pEndpoint);
*pSock = HANDLE_TO_SOCKET(pContext->hAsyncIO);
*pEndpointContext = pContext->pEndpoint->Context;
//
// The buffer not only receives the initial received data, it also
// gets the sock addrs, which must be at least sockaddr_in + 16 bytes
// large
//
g_pfnGetAcceptExSockaddrs( pContext->pvBuff,
(cb = pContext->pEndpoint->InitialRecvSize),
MIN_SOCKADDR_SIZE,
MIN_SOCKADDR_SIZE,
ppsockaddrLocal,
&cbsockaddrLocal,
ppsockaddrRemote,
&cbsockaddrRemote );
*ppvBuff = ( ( cb == 0) ? NULL : pContext->pvBuff);
return;
} // AtqGetAcceptExAddrs()
BOOL
AtqCloseSocket(
PATQ_CONTEXT patqContext,
BOOL fShutdown
)
/*++
Routine Description:
Closes the socket in this atq structure if it wasn't
closed by transmitfile. This function should be called only
if the embedded handle in AtqContext is a Socket.
Arguments:
patqContext - Context whose socket should be closed.
fShutdown - If TRUE, means we call shutdown and always close the socket.
Note that if TransmitFile closed the socket, it will have done the
shutdown for us
Returns:
TRUE on success and FALSE if there is a failure.
--*/
{
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
if ( pContext ) {
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
BOOL fAbortiveClose;
fAbortiveClose = pContext->IsFlag( ACF_ABORTIVE_CLOSE );
pContext->ResetFlag( ACF_ABORTIVE_CLOSE );
//
// Don't delete the socket if we don't have to
//
if ( pContext->IsState( ACS_SOCK_UNCONNECTED |
ACS_SOCK_CLOSED) &&
!pContext->ForceClose()
) {
//
// Do nothing
//
} else {
// default:
// case ACS_SOCK_LISTENING:
// case ACS_SOCK_CONNECTED: {
HANDLE hIO;
PATQ_ENDPOINT pEndpoint;
pEndpoint = pContext->pEndpoint;
pContext->MoveState( ACS_SOCK_CLOSED);
//
// During shutdown, the socket may be closed while this thread
// is doing processing, so only give a warning if any of the
// following fail
//
hIO = (HANDLE )InterlockedExchangePointer(
(PVOID *)&pContext->hAsyncIO,
NULL
);
if ( hIO == NULL ) {
//
// No socket - it is already closed - do nothing.
//
} else {
if (fAbortiveClose || fShutdown ) {
//
// If this is an AcceptEx socket, we must first force a
// user mode context update before we can call shutdown
//
if ( (pEndpoint != NULL) && (pEndpoint->UseAcceptEx) ) {
if ( setsockopt( HANDLE_TO_SOCKET(hIO),
SOL_SOCKET,
SO_UPDATE_ACCEPT_CONTEXT,
(char *) &pEndpoint->ListenSocket,
sizeof(SOCKET) ) == SOCKET_ERROR ) {
ATQ_PRINTF(( DBG_CONTEXT,
"[AtqCloseSocket] Warning- setsockopt "
"failed, error %d, socket = %x,"
" Context= %08x, Listen = %lx\n",
GetLastError(),
hIO,
pContext,
pEndpoint->ListenSocket ));
}
}
} // setsock-opt call
if ( fAbortiveClose ) {
LINGER linger;
linger.l_onoff = TRUE;
linger.l_linger = 0;
if ( setsockopt( HANDLE_TO_SOCKET(hIO),
SOL_SOCKET,
SO_LINGER,
(char *) &linger,
sizeof(linger) ) == SOCKET_ERROR
) {
ATQ_PRINTF(( DBG_CONTEXT,
"[AtqCloseSocket] Warning- setsockopt "
"failed, error %d, socket = %x,"
" Context= %08x, Listen = %lx\n",
GetLastError(),
hIO,
pContext,
pEndpoint->ListenSocket ));
}
else {
ATQ_PRINTF(( DBG_CONTEXT,
"[AtqCloseSocket(%08x)] requested"
" abortive close\n",
pContext));
}
} // set up linger
if ( fShutdown ) {
//
// Note that shutdown can fail in instances where the
// client aborts in the middle of a TransmitFile.
// This is an acceptable failure case
//
shutdown( HANDLE_TO_SOCKET(hIO), 1 );
}
DBG_ASSERT( hIO != NULL);
if ( closesocket( HANDLE_TO_SOCKET(hIO) ) ) {
ATQ_PRINTF(( DBG_CONTEXT,
"[AtqCloseSocket] Warning- closesocket "
" failed, Context = %08x, error %d,"
" socket = %x\n",
pContext,
GetLastError(),
hIO ));
}
} // if (hIO != NULL)
}
return TRUE;
}
DBGPRINTF(( DBG_CONTEXT, "[AtqCloseSocket] Warning - NULL Atq context\n"));
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
} // AtqCloseSocket()
BOOL
AtqCloseFileHandle(
PATQ_CONTEXT patqContext
)
/*++
Routine Description:
Closes the file handle in this atq structure.
This function should be called only if the embedded handle
in AtqContext is a file handle.
Arguments:
patqContext - Context whose file handle should be closed.
Returns:
TRUE on success and FALSE if there is a failure.
Note:
THIS FUNCTIONALITY IS ADDED TO SERVE A SPECIAL REQUEST!!!
Most of the ATQ code thinks that the handle here is a socket.
Except of course this function...
--*/
{
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
if ( pContext != NULL ) {
HANDLE hIO;
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
ATQ_ASSERT( !pContext->IsAcceptExRootContext());
hIO =
(HANDLE ) InterlockedExchangePointer(
(PVOID *)&pContext->hAsyncIO,
NULL
);
if ( (hIO == NULL) || !CloseHandle( hIO ) ) {
ATQ_PRINTF(( DBG_CONTEXT,
"[AtqCloseFileHandle] Warning- CloseHandle failed, "
" Context = %08x, error %d, handle = %x\n",
pContext,
GetLastError(),
hIO ));
}
return TRUE;
}
DBGPRINTF(( DBG_CONTEXT, "[AtqCloseSocket] Warning - NULL Atq context\n"));
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
} // AtqCloseFileHandle()
VOID
AtqFreeContext(
PATQ_CONTEXT patqContext,
BOOL fReuseContext
)
/*++
Routine Description:
Frees the context created in AtqAddAsyncHandle.
Call this after the async handle has been closed and all outstanding
IO operations have been completed. The context is invalid after this call.
Call AtqFreeContext() for same context only ONCE.
Arguments:
patqContext - Context to free
fReuseContext - TRUE if this can context can be reused in the context of
the calling thread. Should be FALSE if the calling thread will exit
soon (i.e., isn't an AtqPoolThread).
--*/
{
PATQ_CONT pContext = (PATQ_CONT)patqContext;
ATQ_ASSERT( pContext != NULL );
IF_DEBUG( API_ENTRY) {
ATQ_PRINTF(( DBG_CONTEXT, "AtqFreeContext( %08x (handle=%08x,"
" nIOs = %d), fReuse=%d)\n",
patqContext, patqContext->hAsyncIO,
pContext->m_nIO,
fReuseContext));
}
if ( pContext ) {
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
if ( fReuseContext ) {
pContext->SetFlag( ACF_REUSE_CONTEXT);
} else {
pContext->ResetFlag( ACF_REUSE_CONTEXT);
}
if ( InterlockedDecrement( &pContext->m_nIO) == 0) {
//
// The number of outstanding ref holders is ZERO.
// Free up this ATQ context.
//
// We really do not free up the context - but try to reuse
// it if possible
//
DBG_ASSERT( pContext->lSyncTimeout == 0);
AtqpReuseOrFreeContext( pContext, fReuseContext);
}
}
return;
} // AtqFreeContext()
BOOL
AtqReadFile(
IN PATQ_CONTEXT patqContext,
IN LPVOID lpBuffer,
IN DWORD BytesToRead,
IN OVERLAPPED * lpo OPTIONAL
)
/*++
Routine Description:
Does an async read using the handle defined in the context.
Arguments:
patqContext - pointer to ATQ context
lpBuffer - Buffer to put read data in
BytesToRead - number of bytes to read
lpo - Overlapped structure to use
Returns:
TRUE on success and FALSE if there is a failure.
--*/
{
BOOL fRes;
DWORD cbRead; // discarded after usage ( since this is Async)
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
PBANDWIDTH_INFO pBandwidthInfo = pContext->m_pBandwidthInfo;
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
ATQ_ASSERT( pBandwidthInfo != NULL );
ATQ_ASSERT( pBandwidthInfo->QuerySignature() == ATQ_BW_INFO_SIGNATURE );
InterlockedIncrement( &pContext->m_nIO);
I_SetNextTimeout(pContext);
pContext->BytesSent = 0;
if ( !lpo ) {
lpo = &pContext->Overlapped;
}
switch ( pBandwidthInfo->QueryStatus( AtqIoRead ) ) {
case StatusAllowOperation:
pBandwidthInfo->IncTotalAllowedRequests();
fRes = ( ReadFile( pContext->hAsyncIO,
lpBuffer,
BytesToRead,
&cbRead,
lpo ) ||
GetLastError() == ERROR_IO_PENDING);
if (!fRes) { InterlockedDecrement( &pContext->m_nIO); };
break;
case StatusBlockOperation:
// store data for restarting the operation.
pContext->arInfo.atqOp = AtqIoRead;
pContext->arInfo.lpOverlapped = lpo;
pContext->arInfo.uop.opReadWrite.buf1.len = BytesToRead;
pContext->arInfo.uop.opReadWrite.buf1.buf = (CHAR * ) lpBuffer;
pContext->arInfo.uop.opReadWrite.dwBufferCount = 1;
pContext->arInfo.uop.opReadWrite.pBufAll = NULL;
// Put this request in queue of blocked requests.
fRes = pBandwidthInfo->BlockRequest( pContext );
if ( fRes )
{
pBandwidthInfo->IncTotalBlockedRequests();
break;
}
// fall through
case StatusRejectOperation:
InterlockedDecrement( &pContext->m_nIO);
pBandwidthInfo->IncTotalRejectedRequests();
SetLastError( ERROR_NETWORK_BUSY);
fRes = FALSE;
break;
default:
ATQ_ASSERT( FALSE);
InterlockedDecrement( &pContext->m_nIO);
SetLastError( ERROR_INVALID_PARAMETER);
fRes = FALSE;
break;
} // switch()
return fRes;
} // AtqReadFile()
BOOL
AtqReadSocket(
IN PATQ_CONTEXT patqContext,
IN LPWSABUF pwsaBuffers,
IN DWORD dwBufferCount,
IN OVERLAPPED * lpo OPTIONAL
)
/*++
Routine Description:
Does an async recv using the handle defined in the context
as a socket.
Arguments:
patqContext - pointer to ATQ context
lpBuffer - Buffer to put read data in
BytesToRead - number of bytes to read
lpo - Overlapped structure to use
Returns:
TRUE on success and FALSE if there is a failure.
--*/
{
BOOL fRes;
DWORD cbRead; // discarded after usage ( since this is Async)
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
PBANDWIDTH_INFO pBandwidthInfo = pContext->m_pBandwidthInfo;
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
ATQ_ASSERT( pBandwidthInfo != NULL );
ATQ_ASSERT( pBandwidthInfo->QuerySignature() == ATQ_BW_INFO_SIGNATURE );
IF_DEBUG(API_ENTRY) {
ATQ_PRINTF(( DBG_CONTEXT,
"AtqReadSocket(%08lx) called.\n", pContext));
}
if (pContext->IsFlag( ACF_RECV_ISSUED)) {
pContext->BytesSent = 0;
pContext->SetFlag( ACF_RECV_CALLED);
#if CC_REF_TRACKING
//
// ATQ notification trace
//
// Notify client context of all non-oplock notification.
// This is for debugging purpose only.
//
// Code 0xf9f9f9f9 indicates a ACF_RECV_CALLED is set in the flags field
//
pContext->NotifyIOCompletion( (ULONG_PTR)pContext, pContext->m_acFlags, 0xf9f9f9f9 );
#endif
return TRUE;
}
I_SetNextTimeout(pContext);
// count the number of bytes
DBG_ASSERT( dwBufferCount >= 1);
pContext->BytesSent = 0;
InterlockedIncrement( &pContext->m_nIO);
if ( !lpo ) {
lpo = &pContext->Overlapped;
}
//
// NYI: Create an optimal function table
//
switch ( pBandwidthInfo->QueryStatus( AtqIoRead ) ) {
case StatusAllowOperation:
{
DWORD dwFlags = 0;
fRes = ( (WSARecv( HANDLE_TO_SOCKET(pContext->hAsyncIO),
pwsaBuffers,
dwBufferCount,
&cbRead,
&dwFlags, // no flags
lpo,
NULL // no completion routine
) == 0) ||
(WSAGetLastError() == WSA_IO_PENDING));
if (!fRes) { InterlockedDecrement( &pContext->m_nIO); }
}
break;
case StatusBlockOperation:
// store data for restarting the operation.
pContext->arInfo.atqOp = AtqIoRead;
pContext->arInfo.lpOverlapped = lpo;
pContext->arInfo.uop.opReadWrite.dwBufferCount = dwBufferCount;
if ( dwBufferCount == 1) {
pContext->arInfo.uop.opReadWrite.buf1.len = pwsaBuffers->len;
pContext->arInfo.uop.opReadWrite.buf1.buf = pwsaBuffers->buf;
pContext->arInfo.uop.opReadWrite.pBufAll = NULL;
} else {
DBG_ASSERT( dwBufferCount > 1);
//
// Inefficient: But we will burn CPU for b/w throttling.
//
WSABUF * pBuf = (WSABUF *)
::LocalAlloc( LPTR, dwBufferCount * sizeof (WSABUF));
if ( NULL != pBuf) {
pContext->arInfo.uop.opReadWrite.pBufAll = pBuf;
CopyMemory( pBuf, pwsaBuffers,
dwBufferCount * sizeof(WSABUF));
} else {
InterlockedDecrement( &pContext->m_nIO);
fRes = FALSE;
break;
}
}
// Put this request in queue of blocked requests.
fRes = pBandwidthInfo->BlockRequest( pContext );
if ( fRes )
{
pBandwidthInfo->IncTotalBlockedRequests();
break;
}
// fall through
case StatusRejectOperation:
InterlockedDecrement( &pContext->m_nIO);
pBandwidthInfo->IncTotalRejectedRequests();
SetLastError( ERROR_NETWORK_BUSY);
fRes = FALSE;
break;
default:
ATQ_ASSERT( FALSE);
InterlockedDecrement( &pContext->m_nIO);
SetLastError( ERROR_INVALID_PARAMETER);
fRes = FALSE;
break;
} // switch()
return fRes;
} // AtqReadSocket()
BOOL
AtqWriteFile(
IN PATQ_CONTEXT patqContext,
IN LPCVOID lpBuffer,
IN DWORD BytesToWrite,
IN OVERLAPPED * lpo OPTIONAL
)
/*++
Routine Description:
Does an async write using the handle defined in the context.
Arguments:
patqContext - pointer to ATQ context
lpBuffer - Buffer to write
BytesToWrite - number of bytes to write
lpo - Overlapped structure to use
Returns:
TRUE on success and FALSE if there is a failure.
--*/
{
BOOL fRes;
DWORD cbWritten; // discarded after usage ( since this is Async)
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
PBANDWIDTH_INFO pBandwidthInfo = pContext->m_pBandwidthInfo;
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
ATQ_ASSERT( pBandwidthInfo != NULL );
ATQ_ASSERT( pBandwidthInfo->QuerySignature() == ATQ_BW_INFO_SIGNATURE );
I_SetNextTimeout(pContext);
pContext->BytesSent = BytesToWrite;
if ( !lpo ) {
lpo = &pContext->Overlapped;
}
InterlockedIncrement( &pContext->m_nIO);
switch ( pBandwidthInfo->QueryStatus( AtqIoWrite) ) {
case StatusAllowOperation:
pBandwidthInfo->IncTotalAllowedRequests();
fRes = ( WriteFile( pContext->hAsyncIO,
lpBuffer,
BytesToWrite,
&cbWritten,
lpo ) ||
GetLastError() == ERROR_IO_PENDING);
if (!fRes) { InterlockedDecrement( &pContext->m_nIO); };
break;
case StatusBlockOperation:
// store data for restarting the operation.
pContext->arInfo.atqOp = AtqIoWrite;
pContext->arInfo.lpOverlapped = lpo;
pContext->arInfo.uop.opReadWrite.buf1.len = BytesToWrite;
pContext->arInfo.uop.opReadWrite.buf1.buf = (CHAR * ) lpBuffer;
pContext->arInfo.uop.opReadWrite.dwBufferCount = 1;
pContext->arInfo.uop.opReadWrite.pBufAll = NULL;
// Put this request in queue of blocked requests.
fRes = pBandwidthInfo->BlockRequest( pContext );
if ( fRes )
{
pBandwidthInfo->IncTotalBlockedRequests();
break;
}
// fall through
case StatusRejectOperation:
InterlockedDecrement( &pContext->m_nIO);
pBandwidthInfo->IncTotalRejectedRequests();
SetLastError( ERROR_NETWORK_BUSY);
fRes = FALSE;
break;
default:
ATQ_ASSERT( FALSE);
InterlockedDecrement( &pContext->m_nIO);
SetLastError( ERROR_INVALID_PARAMETER);
fRes = FALSE;
break;
} // switch()
return fRes;
} // AtqWriteFile()
BOOL
AtqWriteSocket(
IN PATQ_CONTEXT patqContext,
IN LPWSABUF pwsaBuffers,
IN DWORD dwBufferCount,
IN OVERLAPPED * lpo OPTIONAL
)
/*++
Routine Description:
Does an async write using the handle defined in the context as a socket.
Arguments:
patqContext - pointer to ATQ context
pwsaBuffer - pointer to Winsock Buffers for scatter/gather
dwBufferCount - DWORD containing the count of buffers pointed
to by pwsaBuffer
lpo - Overlapped structure to use
Returns:
TRUE on success and FALSE if there is a failure.
--*/
{
BOOL fRes;
DWORD cbWritten; // discarded after usage ( since this is Async)
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
PBANDWIDTH_INFO pBandwidthInfo = pContext->m_pBandwidthInfo;
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
ATQ_ASSERT( pBandwidthInfo != NULL );
ATQ_ASSERT( pBandwidthInfo->QuerySignature() == ATQ_BW_INFO_SIGNATURE );
I_SetNextTimeout(pContext);
//
// count the number of bytes
//
DBG_ASSERT( dwBufferCount >= 1);
pContext->BytesSent = pwsaBuffers->len;
if ( dwBufferCount > 1) {
LPWSABUF pWsaBuf;
for ( pWsaBuf = pwsaBuffers + 1;
pWsaBuf < (pwsaBuffers + dwBufferCount);
pWsaBuf++) {
pContext->BytesSent += pWsaBuf->len;
}
}
if ( lpo == NULL ) {
lpo = &pContext->Overlapped;
}
InterlockedIncrement( &pContext->m_nIO);
switch ( pBandwidthInfo->QueryStatus( AtqIoWrite ) ) {
case StatusAllowOperation:
pBandwidthInfo->IncTotalAllowedRequests();
fRes = ( (WSASend( HANDLE_TO_SOCKET(pContext->hAsyncIO),
pwsaBuffers,
dwBufferCount,
&cbWritten,
0, // no flags
lpo,
NULL // no completion routine
) == 0) ||
(WSAGetLastError() == WSA_IO_PENDING));
if (!fRes) { InterlockedDecrement( &pContext->m_nIO); }
break;
case StatusBlockOperation:
// store data for restarting the operation.
pContext->arInfo.atqOp = AtqIoWrite;
pContext->arInfo.lpOverlapped = lpo;
pContext->arInfo.uop.opReadWrite.dwBufferCount = dwBufferCount;
if ( dwBufferCount == 1) {
pContext->arInfo.uop.opReadWrite.buf1.len = pwsaBuffers->len;
pContext->arInfo.uop.opReadWrite.buf1.buf = pwsaBuffers->buf;
pContext->arInfo.uop.opReadWrite.pBufAll = NULL;
} else {
DBG_ASSERT( dwBufferCount > 1);
//
// Inefficient: But we will burn CPU for b/w throttling.
//
WSABUF * pBuf = (WSABUF *)
::LocalAlloc( LPTR, dwBufferCount * sizeof (WSABUF));
if ( NULL != pBuf) {
pContext->arInfo.uop.opReadWrite.pBufAll = pBuf;
CopyMemory( pBuf, pwsaBuffers,
dwBufferCount * sizeof(WSABUF));
} else {
InterlockedDecrement( &pContext->m_nIO);
fRes = FALSE;
break;
}
}
// Put this request in queue of blocked requests.
fRes = pBandwidthInfo->BlockRequest( pContext );
if ( fRes )
{
pBandwidthInfo->IncTotalBlockedRequests();
break;
}
// fall through
case StatusRejectOperation:
InterlockedDecrement( &pContext->m_nIO);
pBandwidthInfo->IncTotalRejectedRequests();
SetLastError( ERROR_NETWORK_BUSY);
fRes = FALSE;
break;
default:
ATQ_ASSERT( FALSE);
InterlockedDecrement( &pContext->m_nIO);
SetLastError( ERROR_INVALID_PARAMETER);
fRes = FALSE;
break;
} // switch()
return fRes;
} // AtqWriteSocket()
BOOL
AtqSyncWsaSend(
IN PATQ_CONTEXT patqContext,
IN LPWSABUF pwsaBuffers,
IN DWORD dwBufferCount,
OUT LPDWORD pcbWritten
)
/*++
Routine Description:
Does a sync write of an array of wsa buffers using WSASend.
Arguments:
patqContext - pointer to ATQ context
pwsaBuffer - pointer to Winsock Buffers for scatter/gather
dwBufferCount - DWORD containing the count of buffers pointed
to by pwsaBuffer
pcbWritten - ptr to count of bytes written
Returns:
TRUE on success and FALSE if there is a failure.
--*/
{
BOOL fRes = FALSE;
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
fRes = ( WSASend( HANDLE_TO_SOCKET(pContext->hAsyncIO),
pwsaBuffers,
dwBufferCount,
pcbWritten,
0, // no flags
NULL, // lpo == NULL for sync write
NULL // no completion routine
) == 0);
return fRes;
} // AtqSyncWsaSend()
BOOL
AtqTransmitFile(
IN PATQ_CONTEXT patqContext,
IN HANDLE hFile,
IN DWORD dwBytesInFile,
IN LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,
IN DWORD dwFlags
)
/*++
Routine Description:
Does a TransmitFile using the handle defined in the context.
Arguments:
patqContext - pointer to ATQ context
hFile - handle of file to read from
dwBytesInFile - Bytes to transmit
lpTransmitBuffers - transmit buffer structure
dwFlags - Transmit file flags
Returns:
TRUE on success and FALSE if there is a failure.
--*/
{
BOOL fRes;
PATQ_CONT pContext = (PATQ_CONT) patqContext;
PBANDWIDTH_INFO pBandwidthInfo = pContext->m_pBandwidthInfo;
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
ATQ_ASSERT( pBandwidthInfo != NULL );
ATQ_ASSERT( pBandwidthInfo->QuerySignature() == ATQ_BW_INFO_SIGNATURE );
//
// For large file sends, the client's default timeout may not be
// adequte for slow links. Scale based on bytes being sent
//
I_SetNextTimeout(pContext);
pContext->BytesSent = dwBytesInFile;
if ( dwFlags == 0 ) {
//
// If no flags are set, then we can attempt to use the special
// write-behind flag. This flag can cause the TransmitFile to
// complete immediately, before the send actually completes.
// This can be a significant performance improvement inside the
// system.
//
dwFlags = TF_WRITE_BEHIND;
} else if ( dwFlags & TF_DISCONNECT ) {
//
// If the socket is getting disconnected, mark it appropriately
//
pContext->MoveState( ( ( dwFlags & TF_REUSE_SOCKET )?
ACS_SOCK_UNCONNECTED:
ACS_SOCK_CLOSED
)
);
}
//
// Use kernel apc flag unless configured not to
//
if ( g_fUseKernelApc ) {
dwFlags |= TF_USE_KERNEL_APC;
}
InterlockedIncrement( &pContext->m_nIO);
switch ( pBandwidthInfo->QueryStatus( AtqIoXmitFile ) ) {
case StatusAllowOperation:
pBandwidthInfo->IncTotalAllowedRequests();
fRes = (g_pfnTransmitFile( HANDLE_TO_SOCKET(pContext->hAsyncIO),
hFile,
dwBytesInFile,
0,
&pContext->Overlapped,
lpTransmitBuffers,
dwFlags ) ||
(GetLastError() == ERROR_IO_PENDING));
if (!fRes) { InterlockedDecrement( &pContext->m_nIO); }
break;
case StatusBlockOperation:
// store data for restarting the operation.
pContext->arInfo.atqOp = AtqIoXmitFile;
pContext->arInfo.lpOverlapped = &pContext->Overlapped;
pContext->arInfo.uop.opXmit.hFile = hFile;
pContext->arInfo.uop.opXmit.dwBytesInFile = dwBytesInFile;
pContext->arInfo.uop.opXmit.lpXmitBuffers = lpTransmitBuffers;
pContext->arInfo.uop.opXmit.dwFlags = dwFlags;
// Put this request in queue of blocked requests.
fRes = pBandwidthInfo->BlockRequest( pContext);
if ( fRes )
{
pBandwidthInfo->IncTotalBlockedRequests();
break;
}
// fall through
case StatusRejectOperation:
InterlockedDecrement( &pContext->m_nIO);
pBandwidthInfo->IncTotalRejectedRequests();
SetLastError( ERROR_NETWORK_BUSY);
fRes = FALSE;
break;
default:
ATQ_ASSERT( FALSE);
InterlockedDecrement( &pContext->m_nIO);
SetLastError( ERROR_INVALID_PARAMETER);
fRes = FALSE;
break;
} // switch()
//
// Restore the socket state if we failed so that the handle gets freed
//
if ( !fRes )
{
pContext->MoveState( ACS_SOCK_CONNECTED);
}
return fRes;
} // AtqTransmitFile()
BOOL
AtqTransmitFileEx(
IN PATQ_CONTEXT patqContext,
IN HANDLE hFile,
IN DWORD dwBytesInFile,
IN LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,
IN DWORD dwFlags,
IN OVERLAPPED * lpo
)
/*++
Routine Description:
Does a TransmitFile using the handle defined in the context.
Uses the parameter lpo instead of the structure in the context.
Arguments:
patqContext - pointer to ATQ context
hFile - handle of file to read from
dwBytesInFile - Bytes to transmit
lpTransmitBuffers - transmit buffer structure
dwFlags - Transmit file flags
lpo - overlapped structure
Returns:
TRUE on success and FALSE if there is a failure.
--*/
{
BOOL fRes = TRUE;
PATQ_CONT pContext = (PATQ_CONT) patqContext;
PBANDWIDTH_INFO pBandwidthInfo = pContext->m_pBandwidthInfo;
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
ATQ_ASSERT( pBandwidthInfo != NULL );
ATQ_ASSERT( pBandwidthInfo->QuerySignature() == ATQ_BW_INFO_SIGNATURE );
//
// For large file sends, the client's default timeout may not be
// adequte for slow links. Scale based on bytes being sent
//
I_SetNextTimeout(pContext);
pContext->BytesSent = dwBytesInFile;
if ( dwFlags == 0 ) {
//
// If no flags are set, then we can attempt to use the special
// write-behind flag. This flag can cause the TransmitFile to
// complete immediately, before the send actually completes.
// This can be a significant performance improvement inside the
// system.
//
dwFlags = TF_WRITE_BEHIND;
} else if ( dwFlags & TF_DISCONNECT ) {
//
// If the socket is getting disconnected, mark it appropriately
//
pContext->MoveState( ( ( dwFlags & TF_REUSE_SOCKET )?
ACS_SOCK_UNCONNECTED:
ACS_SOCK_CLOSED
)
);
}
InterlockedIncrement( &pContext->m_nIO);
if ( (StatusAllowOperation == pBandwidthInfo->QueryStatus( AtqIoXmitFile ) ) ) {
pBandwidthInfo->IncTotalAllowedRequests();
fRes = (g_pfnTransmitFile( HANDLE_TO_SOCKET(pContext->hAsyncIO),
hFile,
dwBytesInFile,
0,
(lpo == NULL) ? &pContext->Overlapped : lpo,
lpTransmitBuffers,
dwFlags ) ||
(GetLastError() == ERROR_IO_PENDING));
if (!fRes) { InterlockedDecrement( &pContext->m_nIO); };
} else {
ASSERT(FALSE);
fRes = FALSE;
}
//
// Restore the socket state if we failed so that the handle gets freed
//
if ( !fRes )
{
pContext->MoveState( ACS_SOCK_CONNECTED);
}
return fRes;
} // AtqTransmitFileEx()
BOOL
AtqReadDirChanges(IN PATQ_CONTEXT patqContext,
IN LPVOID lpBuffer,
IN DWORD BytesToRead,
IN BOOL fWatchSubDir,
IN DWORD dwNotifyFilter,
IN OVERLAPPED * lpo
)
/*++
AtqReadDirChanges()
Description:
This function submits an Async ReadDirectoryChanges() call for
the Async handle in the ATQ context supplied.
It always requires a non-NULL overlapped pointer for processing
this call.
Arguments:
patqContext - pointer to ATQ Context
lpBuffer - buffer for the data to be read from ReadDirectoryChanges()
BytesToRead - count of bytes to read into buffer
fWatchSubDir - should we watch for sub directory changes
dwNotifyFilter - DWORD containing the flags for Notification
lpo - pointer to overlapped structure.
Returns:
TRUE if ReadDirectoryChanges() is successfully submitted.
FALSE if there is any failure in submitting IO.
--*/
{
BOOL fRes;
DWORD cbRead; // discarded after usage ( since this is Async)
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
if ( g_pfnReadDirChangesW == NULL ) {
ATQ_PRINTF((DBG_CONTEXT,"ReadDirChanges entry point NULL\n"));
SetLastError(ERROR_NOT_SUPPORTED);
return(FALSE);
}
if ( lpo == NULL ) {
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
I_SetNextTimeout(pContext);
pContext->BytesSent = 0;
InterlockedIncrement( &pContext->m_nIO);
fRes = g_pfnReadDirChangesW( pContext->hAsyncIO,
lpBuffer,
BytesToRead,
fWatchSubDir,
dwNotifyFilter,
&cbRead,
lpo,
NULL);
if (!fRes) { InterlockedDecrement( &pContext->m_nIO); };
return fRes;
} // AtqReadDirChanges()
BOOL
AtqPostCompletionStatus(
IN PATQ_CONTEXT patqContext,
IN DWORD BytesTransferred
)
/*++
Routine Description:
Posts a completion status on the completion port queue
An IO pending error code is treated as a success error code
Arguments:
patqContext - pointer to ATQ context
Everything else as in the Win32 API
NOTES:
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
--*/
{
BOOL fRes;
PATQ_CONT pAtqContext = (PATQ_CONT ) patqContext;
PBANDWIDTH_INFO pBandwidthInfo = pAtqContext->m_pBandwidthInfo;
ATQ_ASSERT( (pAtqContext)->Signature == ATQ_CONTEXT_SIGNATURE );
ATQ_ASSERT( pBandwidthInfo != NULL );
ATQ_ASSERT( pBandwidthInfo->QuerySignature() == ATQ_BW_INFO_SIGNATURE );
if ( !pAtqContext->IsBlocked()) {
InterlockedIncrement( &pAtqContext->m_nIO);
fRes = ( g_pfnPostCompletionStatus( g_hCompPort,
BytesTransferred,
(ULONG_PTR)patqContext,
&pAtqContext->Overlapped ) ||
(GetLastError() == ERROR_IO_PENDING));
if (!fRes) { InterlockedDecrement( &pAtqContext->m_nIO); };
} else {
//
// Forcibly remove the context from blocking list.
//
fRes = pBandwidthInfo->RemoveFromBlockedList(pAtqContext);
// There is a possibility of race conditions!
// If we cant remove an item from blocking list before
// its IO operation is scheduled.
// there wont be any call back generated for this case!
}
return fRes;
} // AtqPostCompletionStatus
DWORD
I_AtqGetGlobalConfiguration(VOID)
/*++
Description:
This function sets several global config params for the ATQ package.
It also reads the global configuration from registry for ATQ.
The values if present will override the defaults
Returns:
Win32 Errorcode - NO_ERROR on success and anything else for error
--*/
{
DWORD dwError = NO_ERROR;
DWORD dwDefaultThreadTimeout = ATQ_REG_DEF_THREAD_TIMEOUT;
//
// If this is a NTW, do the right thing
//
if ( !TsIsNtServer() ) {
g_cMaxThreadLimit = ATQ_REG_MIN_POOL_THREAD_LIMIT;
} else {
MEMORYSTATUS ms;
//
// get the memory size
//
ms.dwLength = sizeof(MEMORYSTATUS);
GlobalMemoryStatus( &ms );
//
// Alloc two threads per MB of memory.
//
g_cMaxThreadLimit = (LONG)((ms.dwTotalPhys >> 19) + 2);
if ( g_cMaxThreadLimit < ATQ_REG_MIN_POOL_THREAD_LIMIT ) {
g_cMaxThreadLimit = ATQ_REG_MIN_POOL_THREAD_LIMIT;
} else if ( g_cMaxThreadLimit > ATQ_REG_MAX_POOL_THREAD_LIMIT ) {
g_cMaxThreadLimit = ATQ_REG_MAX_POOL_THREAD_LIMIT;
}
}
//
// Get entry points for NT
//
if ( !I_AtqInitializeNtEntryPoints( ) ) {
dwError = ERROR_MOD_NOT_FOUND;
return ( dwError);
}
g_pfnCreateCompletionPort = CreateIoCompletionPort;
g_pfnGetQueuedCompletionStatus = GetQueuedCompletionStatus;
g_pfnCloseCompletionPort = CloseHandle;
g_pfnPostCompletionStatus = PostQueuedCompletionStatus;
HKEY hkey = NULL;
DWORD dwVal;
dwError = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
g_PSZ_ATQ_CONFIG_PARAMS_REG_KEY,
0,
KEY_READ,
&hkey);
if ( dwError == NO_ERROR ) {
//
// Read the Concurrency factor per processor
//
dwVal = I_AtqReadRegDword( hkey,
ATQ_REG_PER_PROCESSOR_CONCURRENCY,
ATQ_REG_DEF_PER_PROCESSOR_CONCURRENCY);
AtqSetInfo( AtqMaxConcurrency, (ULONG_PTR)dwVal);
//
// Read the count of threads to be allowed per processor
//
dwVal = I_AtqReadRegDword( hkey,
ATQ_REG_PER_PROCESSOR_ATQ_THREADS,
ATQ_REG_DEF_PER_PROCESSOR_ATQ_THREADS
);
if ( dwVal != 0 ) {
AtqSetInfo( AtqMaxPoolThreads, (ULONG_PTR)dwVal);
}
//
// Read the Data transfer rate value for our calculations
//
dwVal = I_AtqReadRegDword( hkey,
ATQ_REG_MIN_KB_SEC,
ATQ_REG_DEF_MIN_KB_SEC );
AtqSetInfo( AtqMinKbSec, (ULONG_PTR)dwVal);
//
// read the max thread limit
//
g_cMaxThreadLimit = I_AtqReadRegDword( hkey,
ATQ_REG_POOL_THREAD_LIMIT,
g_cMaxThreadLimit);
//
// read the listen backlog
//
g_cListenBacklog = I_AtqReadRegDword( hkey,
ATQ_REG_LISTEN_BACKLOG,
g_cListenBacklog);
//
// Read the time (in seconds) of how long the threads
// can stay alive when there is no IO operation happening on
// that thread.
//
dwVal = I_AtqReadRegDword( hkey,
ATQ_REG_THREAD_TIMEOUT,
dwDefaultThreadTimeout
);
AtqSetInfo( AtqThreadTimeout, (ULONG_PTR)dwVal);
//
// See if we need to turn off TF_USE_KERNEL_APC flag for
// TransmitFile
//
dwVal = I_AtqReadRegDword( hkey,
ATQ_REG_USE_KERNEL_APC,
ATQ_REG_DEF_USE_KERNEL_APC );
g_fUseKernelApc = dwVal;
//
// Whether or not to enable debug thread creator
//
dwVal = I_AtqReadRegDword( hkey,
ATQ_REG_ENABLE_DEBUG_THREADS,
g_fEnableDebugThreads );
g_fEnableDebugThreads = !!dwVal;
//
// Do we want to run this backlog monitor at all?
//
dwVal = I_AtqReadRegDword( hkey,
ATQ_REG_DISABLE_BACKLOG_MONITOR,
g_fDisableBacklogMonitor );
g_fDisableBacklogMonitor = !!dwVal;
//
// Read timeout for backlog monitor
//
dwVal = I_AtqReadRegDword( hkey,
ATQ_REG_FORCE_TIMEOUT,
g_cForceTimeout );
g_cForceTimeout = dwVal;
ATQ_REQUIRE( !RegCloseKey( hkey ) );
hkey = NULL;
}
DBG_ASSERT( NULL == hkey);
return ( dwError);
} // I_AtqGetGlobalConfiguration()
DWORD
I_NumAtqEndpointsOpen(VOID)
/*++
Description:
This function counts the number of Enpoints that remain open.
Arguments:
None
Returns:
DWORD containing the number of endpoints that are open.
--*/
{
DWORD nEPOpen = 0;
AcquireLock( &AtqEndpointLock);
PLIST_ENTRY plEP;
for( plEP = AtqEndpointList.Flink;
plEP != &AtqEndpointList;
plEP = plEP->Flink ) {
nEPOpen++;
} // for
ReleaseLock( &AtqEndpointLock);
return ( nEPOpen);
} // I_NumAtqEndpointsOpen()
//
// Global functions
//
DWORD
I_AtqReadRegDword(
IN HKEY hkey,
IN LPCSTR pszValueName,
IN DWORD dwDefaultValue )
/*++
NAME: I_AtqReadRegDword
SYNOPSIS: Reads a DWORD value from the registry.
ENTRY: hkey - Openned registry key to read
pszValueName - The name of the value.
dwDefaultValue - The default value to use if the
value cannot be read.
RETURNS DWORD - The value from the registry, or dwDefaultValue.
--*/
{
DWORD err;
DWORD dwBuffer;
DWORD cbBuffer = sizeof(dwBuffer);
DWORD dwType;
if( hkey != NULL ) {
err = RegQueryValueExA( hkey,
pszValueName,
NULL,
&dwType,
(LPBYTE)&dwBuffer,
&cbBuffer );
if( ( err == NO_ERROR ) && ( dwType == REG_DWORD ) ) {
dwDefaultValue = dwBuffer;
}
}
return dwDefaultValue;
} // I_AtqReadRegDword()
BOOL
AtqSetSocketOption(
IN PATQ_CONTEXT patqContext,
IN INT optName,
IN INT optValue
)
/*++
AtqSetSocketOption()
Routine Description:
Set socket options. Presently only handles TCP_NODELAY
Arguments:
patqContext - pointer to ATQ context
optName - name of property to change
optValue - value of property to set
Return Value:
TRUE if successful, else FALSE
--*/
{
if (TCP_NODELAY != optName)
{
return FALSE;
}
PATQ_CONT pContext = (PATQ_CONT)patqContext;
ATQ_ASSERT( pContext != NULL );
IF_DEBUG( API_ENTRY) {
ATQ_PRINTF(( DBG_CONTEXT, "AtqSetSocketOption( %08x (handle=%08x,"
" nIOs = %d), optName=%d, optValue=%d)\n",
patqContext, patqContext->hAsyncIO,
pContext->m_nIO, optName, optValue));
}
if ( pContext ) {
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
if ( ((BOOL)pContext->IsFlag(ACF_TCP_NODELAY)) == ((BOOL)optValue))
{
//
// The flag is already enabled. Return success.
//
return TRUE;
}
else
{
if (NO_ERROR == setsockopt( HANDLE_TO_SOCKET(pContext->hAsyncIO),
IPPROTO_TCP,
TCP_NODELAY,
(char *)&optValue,
sizeof(INT)))
{
if ( optValue)
{
pContext->SetFlag(ACF_TCP_NODELAY);
}
else
{
pContext->ResetFlag(ACF_TCP_NODELAY);
}
return TRUE;
}
}
}
return FALSE;
}
PIIS_CAP_TRACE_INFO
AtqGetCapTraceInfo(
IN PATQ_CONTEXT patqContext
)
{
PATQ_CONT pContext = (PATQ_CONT)patqContext;
ATQ_ASSERT( pContext != NULL );
return pContext->GetCapTraceInfo();
}
DWORD
AtqDebugCreatorThread(
LPDWORD param
)
/*++
Routine Description:
For debugging purpose.
This function will cause another IO thread to be created (regardless of
the max thread limit). This IO thread will only serve new connections.
To trigger the creation of a new thread, set isatq!g_fCreateDebugThread=1
Arguments:
param - Unused
Return Value:
ERROR_SUCCESS
--*/
{
for ( ; !g_fShutdown ; )
{
Sleep( 5000 );
if ( g_fCreateDebugThread )
{
OutputDebugString( "Creating DEBUG thread." \
"Reseting isatq!g_fCreateDebugThread\n" );
I_AtqCheckThreadStatus( (VOID*) ATQ_DEBUG_THREAD );
g_fCreateDebugThread = FALSE;
}
}
return ERROR_SUCCESS;
}
/************************ End of File ***********************/