Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

2214 lines
55 KiB

/*++
Copyright (c) 1994 Microsoft Corporation
Module Name :
atqsupp.c
Abstract:
Contains internal support routines for the ATQ package
Author:
Johnson Apacible (johnsona) 11-Mar-1996
(Contains code from atqnew.c)
Revision:
Murali R. Krishnan (MuraliK) 02-Apr-1996
- code clarity; path-length mods & ref count fixes
--*/
# include <tcpdllp.hxx>
# include <tsproc.hxx>
# include "atqtypes.hxx"
# include <inetreg.h>
# include "tcpproc.h"
# include "dbgutil.h"
# include <tssched.hxx>
# include "auxctrs.h"
//
// Globals
//
# ifdef IIS_AUX_COUNTERS
LONG g_AuxCounters[NUM_AUX_COUNTERS];
# endif // IIS_AUX_COUNTERS
DWORD AtqCurrentTick = 1;
/************************************************************
* Functions
************************************************************/
inline VOID
AddOutstandingAcceptExSockets(
ACCEPTEX_LISTEN_INFO * pListenInfo
)
/*++
Routine Description:
Adds additional AcceptEx sockets to the specified acceptex listen socket
Arguments:
pListenInfo - AcceptEx socket to add additional sockets to
--*/
{
(VOID ) I_AtqPrepareAcceptExSockets(pListenInfo,
pListenInfo->cNewIncrement
);
return;
} // AddOutstandingAcceptExSockets()
VOID
I_InitDefaultAtqContext(IN PATQ_CONT pContext,
IN ATQ_COMPLETION pfnCompletion,
IN DWORD TimeOut,
IN HANDLE hAsyncIO)
{
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE);
pContext->pfnCompletion = pfnCompletion;
pContext->TimeOut = TimeOut;
pContext->TimeOutScanID = 0;
pContext->hAsyncIO = hAsyncIO;
ZeroMemory(
&pContext->Overlapped,
sizeof( pContext->Overlapped )
);
//
// Following added for bandwidth throttling purposes
//
pContext->fBlocked = FALSE;
pContext->arInfo.atqOp = AtqIoNone;
pContext->arInfo.lpOverlapped = NULL;
// bandwidth throttling initialization ends here.
} // I_InitDefaultAtqContext()
inline VOID
I_InitNonAcceptExContext(IN PATQ_CONT pContext, IN PVOID pClientContext)
{
//
// Note that if we're not using AcceptEx,
// then we consider the client
// to have been notified externally (thus fConnectionIndicated is
// set to TRUE).
// Also we set the next timeout to be infinite, which may be reset
// when the next IO is submitted.
//
pContext->NextTimeout = ATQ_INFINITE;
pContext->fConnectionIndicated = TRUE;
pContext->SockState = ATQ_SOCK_CONNECTED;
pContext->ClientContext = pClientContext;
pContext->pfnConnComp = NULL;
pContext->pListenInfo = NULL;
pContext->fAcceptExContext = FALSE;
//
// Insert this to the proper list
//
ATQ_ASSERT( pContext->ContextList != NULL);
pContext->ContextList->InsertIntoActiveList( &pContext->ListEntry );
return;
} // I_InitNonAcceptExContext()
inline VOID
I_InitAcceptExContext( IN PATQ_CONT pContext, IN DWORD NextTimeOut)
{
pContext->NextTimeout = NextTimeOut;
pContext->fConnectionIndicated = FALSE;
pContext->SockState = ATQ_SOCK_LISTENING;
pContext->ClientContext = NULL;
pContext->fAcceptExContext = IS_ACCEPT_EX_CONTEXT(pContext);
pContext->lSyncTimeout = AtqPendingIo;
//
// Add it to the pending accept ex list
//
ATQ_ASSERT( pContext->ContextList != NULL);
pContext->ContextList->InsertIntoPendingList( &pContext->ListEntry);
return;
} // I_InitAcceptExContext()
inline VOID
I_InitAtqContextSetBuffer(IN PATQ_CONT pContext,
IN PVOID pvBuff,
IN DWORD cbBuffer)
{
pContext->pvBuff = pvBuff;
pContext->cbBuff = cbBuffer;
return;
} // I_InitAtqContextSetBuffer()
VOID
AtqValidateProductType(
VOID
)
{
DWORD err;
HKEY hKey;
DWORD dwType;
DWORD dataSize;
LARGE_INTEGER data;
PCHAR systemPrefixName = "yjgfsqnfutzt";
CHAR tmpBuffer[16];
DWORD len;
DWORD i;
//
// If it is not claiming to be an NT server, then
// we're ok.
//
if ( !TsIsNtServer( ) ) {
return;
}
//
// Get the system prefix bit string
//
err = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
"System\\Setup",
0,
KEY_ALL_ACCESS,
&hKey
);
if ( err != ERROR_SUCCESS ) {
ATQ_PRINTF((buff,"Cannot find system setup key\n"));
goto is_ntwksta;
}
//
// prepare breakfast by scrambling the egg
//
len = strlen(systemPrefixName);
for ( i=0; i < len ; i++ ) {
tmpBuffer[i] = systemPrefixName[len-i-1] - 1;
}
tmpBuffer[len] = '\0';
dataSize = sizeof(data);
err = RegQueryValueEx(
hKey,
tmpBuffer,
NULL,
&dwType,
(PUCHAR)&data,
&dataSize
);
RegCloseKey(hKey);
if ( (err == ERROR_SUCCESS) && (dwType == REG_BINARY) ) {
if ( (data.HighPart & 0x04000000) == 0 ) {
//
// We are not a server!
//
ATQ_PRINTF((buff,"Invalid server pattern %x\n",
data.HighPart));
goto is_ntwksta;
}
return;
} else {
ATQ_PRINTF((buff,"Cannot find the reg key %s\n", tmpBuffer));
}
is_ntwksta:
ATQ_PRINTF((buff,"Changing platform type from %d to %d\n",
TsPlatformType, PtNtWorkstation));
TsPlatformType = PtNtWorkstation;
return;
} // AtqValidateProductType
/**************************************************
* Allocation Caching of ATQ contexts
* Author: Murali R. Krishnan (MuraliK) 5-July-1995
**************************************************/
BOOL AtqInitAllocCachedContexts( VOID)
/*++
This function initializes allocation caching of Atq contexts to be
used for holding free atq contexts for reuse.
The main purpose of allocation cache is to avoid calls to the Heap allocator
whenever a new ATQ context needs to be allocated. This reduces the
contention at the heap's critical section.
Presently this initializes a single list head to hold free list of contexts
Since this function is called at initialization of ATQ module,
there is no or less contention. Hence, we can allocate a certain fixed
number of initial contexts to avoid slow starts. Defered for now.
Arguments:
NONE
Returns:
TRUE on success and FALSE on failure.
--*/
{
InitializeListHead(&AtqFreeContextList);
InitializeCriticalSection( &AtqFreeContextListLock );
return ( TRUE);
} // AtqInitAllocCachedContexts()
VOID
AtqFreeAllocCachedContexts( VOID)
/*++
This function frees all the Atq contexts that were alloc cached.
It walks through the list of alloc cached contexts and frees them.
This function should be called when ATQ module is terminated and when
no other thread can interfere in processing a shared object.
Arguments:
NONE
Returns:
TRUE on success and FALSE on failure.
--*/
{
PLIST_ENTRY pEntry;
PATQ_CONT pAtqContext;
AcquireLock( &AtqFreeContextListLock );
pEntry = RemoveHeadList( &AtqFreeContextList );
while ( pEntry != &AtqFreeContextList ) {
pAtqContext = CONTAINING_RECORD(
pEntry,
ATQ_CONTEXT,
ListEntry
);
ATQ_ASSERT(pAtqContext->Signature == ATQ_FREE_SIGNATURE);
LocalFree(pAtqContext); // free the context
pEntry = RemoveHeadList( &AtqFreeContextList );
}
ATQ_ASSERT( IsListEmpty(&AtqFreeContextList) );
ReleaseLock( &AtqFreeContextListLock );
DeleteCriticalSection( &AtqFreeContextListLock );
return;
} // AtqFreeAllocCachedContexts
PATQ_CONT
AtqAllocContextFromCache( VOID)
/*++
This function attempts to allocate an ATQ context from the allocation cache
using the free list of atq contexts available.
If none is available, then a new context is allocated using LocalAlloc() and
returned to caller. Eventually the context will enter free list and will
be available for free use.
Arguments:
None
Returns:
On success a valid pointer to ATQ_CONT. Otherwise NULL.
--*/
{
PLIST_ENTRY pEntry;
PATQ_CONT pAtqContext;
AcquireLock( &AtqFreeContextListLock );
pEntry = RemoveHeadList(&AtqFreeContextList);
ReleaseLock( &AtqFreeContextListLock );
if ( pEntry != &AtqFreeContextList ) {
#if DBG
pEntry->Flink = pEntry->Blink = NULL;
#endif
// There is a free entry.
pAtqContext = CONTAINING_RECORD(
pEntry,
ATQ_CONTEXT,
ListEntry
);
pAtqContext->Signature = ATQ_SIGNATURE;
ATQ_ASSERT( pAtqContext->lSyncTimeout == AtqIdle);
ATQ_ASSERT( pAtqContext->fInTimeout == FALSE);
AcIncrement( AacAtqContextsAlloced);
} else {
pAtqContext = (PATQ_CONT)LocalAlloc( LPTR, sizeof(ATQ_CONTEXT));
if ( pAtqContext != NULL ) {
DWORD index = ++AtqGlobalContextCount % ATQ_NUM_CONTEXT_LIST;
pAtqContext->ContextList = &AtqActiveContextList[index];
pAtqContext->Signature = ATQ_SIGNATURE;
pAtqContext->lSyncTimeout = AtqIdle;
pAtqContext->fInTimeout = FALSE;
AcIncrement( AacAtqContextsAlloced);
}
}
return (pAtqContext);
} // AtqAllocContextFromCache()
VOID
I_AtqFreeContextToCache(
IN PATQ_CONT pAtqContext,
IN BOOL UnlinkContext
)
/*++
This function releases the given context to the allocation cache.
It adds the given context object to list of context objects on free list.
Arguments:
pAtqContext pointer to the ATQ_CONTEXT that is being freed.
Returns:
None
Issues:
Should we limit the number of items that can be on free list and
to release the remaining to global pool? NYI (depends on # CPUs)
--*/
{
PLIST_ENTRY pEntry;
ATQ_ASSERT( pAtqContext->Signature == ATQ_SIGNATURE );
//
// Delete the socket handle if it's not already deleted
//
if ( pAtqContext->hAsyncIO != NULL ) {
SOCKET hIO =
(SOCKET ) InterlockedExchange( (LPLONG) &pAtqContext->hAsyncIO,
NULL);
if ( hIO != NULL &&
(closesocket( hIO ) == SOCKET_ERROR )
) {
ATQ_PRINTF((buff,
"[AtqFreeContextToCache] Warning - Context=%08x, "
" closesocket failed, error %d, socket = %x\n",
pAtqContext,
GetLastError(),
hIO ));
}
}
ATQ_ASSERT( pAtqContext->hAsyncIO == NULL);
//
// Deref the listen info if this context is associated with one
//
if ( pAtqContext->pListenInfo ) {
LONG lNoListner =
InterlockedDecrement( (PLONG)&pAtqContext->pListenInfo->cRef );
if ( lNoListner == 0 ) {
ATQ_ASSERT( pAtqContext->pListenInfo->Signature ==
ACCEPTEX_LISTEN_SIGN );
ATQ_ASSERT( pAtqContext->pListenInfo->ListEntry.Flink == NULL);
ATQ_ASSERT( pAtqContext->pListenInfo->cRef == 0);
pAtqContext->pListenInfo->Signature = ACCEPTEX_LISTEN_SIGN_FREE;
LocalFree( pAtqContext->pListenInfo );
}
pAtqContext->pListenInfo = NULL;
}
if ( pAtqContext->pvBuff ) {
LocalFree( pAtqContext->pvBuff );
pAtqContext->pvBuff = NULL;
}
if ( UnlinkContext ) {
ATQ_ASSERT( pAtqContext->ContextList != NULL);
pAtqContext->ContextList->RemoveFromList( &pAtqContext->ListEntry);
#if DBG
pAtqContext->ListEntry.Flink = pAtqContext->ListEntry.Flink = NULL;
#endif
}
pAtqContext->Signature = ATQ_FREE_SIGNATURE;
AcquireLock( &AtqFreeContextListLock );
InsertTailList(
&AtqFreeContextList,
&pAtqContext->ListEntry
);
AcDecrement( AacAtqContextsAlloced);
pAtqContext->lSyncTimeout = AtqIdle;
pAtqContext->fInTimeout = FALSE;
ReleaseLock( &AtqFreeContextListLock );
return;
} // I_AtqFreeContextToCache
DWORD
AtqPoolThread(
LPDWORD param
)
/*++
Routine Description:
This is the pool thread wait and dispatch routine
Arguments:
param : unused.
Return Value:
Thread return value (ignored)
--*/
{
PATQ_CONT pContext;
BOOL fRet;
LPOVERLAPPED lpo;
DWORD cbWritten;
DWORD returnValue;
DWORD availThreads;
LONG atqSyncTimeout;
for(;;) {
InterlockedIncrement( (PLONG)&g_cAvailableThreads );
fRet = GetQueuedCompletionStatus( g_hCompPort,
&cbWritten,
(LPDWORD)&pContext,
&lpo,
g_msThreadTimeout );
availThreads = InterlockedDecrement( (PLONG)&g_cAvailableThreads );
if ( fRet || lpo ) {
if ( pContext == NULL && g_fShutdown ) {
//
// This is our signal to exit.
//
returnValue = NO_ERROR;
break;
}
ATQ_ASSERT( lpo );
//
// If this is an AcceptEx listen socket atq completion, then the
// client Atq context we really want is keyed from the overlapped
// structure that is stored in the client's Atq context.
//
// Note that if AcceptEx is *not* being used, pListenInfo
// will always be NULL so check that first
//
if ( pContext->fAcceptExContext ) {
pContext = CONTAINING_RECORD( lpo,
ATQ_CONTEXT,
Overlapped );
}
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE );
//
// Busy wait for timeout processing to complete!
// This is ugly :( A fix in time for IIS 2.0/Catapult 1.0 release
//
atqSyncTimeout = InterlockedExchange( &pContext->lSyncTimeout,
AtqProcessingIo);
if ( AtqProcessingTimeout == atqSyncTimeout ) {
while ( pContext->fInTimeout) {
AcIncrement( CacAtqWaitsForTimeout);
Sleep( ATQ_WAIT_FOR_TIMEOUT_PROCESSING);
};
}
ATQ_ASSERT( AtqIdle != atqSyncTimeout);
//
// Make sure we're not running out of threads
//
if ( availThreads == 0 ) {
//
// Make sure there are pool threads to service the request
//
(VOID)I_AtqCheckThreadStatus();
}
//
// We need to make sure the timeout thread doesn't time this
// request out so reset the timeout value
//
InterlockedExchange( (LPLONG )&pContext->NextTimeout,
(LONG ) ATQ_INFINITE);
//
// Update Bandwidth information on successful completion, if needed
//
if ( g_fBandwidthThrottle && fRet && cbWritten > 0) {
//this will have problems when we use XmitFile for large files.
AbwUpdateBytesXfered(pContext, cbWritten);
}
//
// Is this a connection indication?
//
if ( !pContext->fConnectionIndicated ) {
ACCEPTEX_LISTEN_INFO * pListenInfo = pContext->pListenInfo;
ATQ_ASSERT( pListenInfo != NULL );
//
// Indicate this socket is in use
//
InterlockedDecrement( (PLONG)&pListenInfo->cSocketsAvail );
//
// If we're running low on sockets, add some more now
//
if ( pListenInfo->cSocketsAvail <
pListenInfo->cNewIncrement ) {
AddOutstandingAcceptExSockets( pListenInfo );
}
//
// If an error occurred on this completion,
// shutdown the socket
//
if ( !fRet ) {
DBGPRINTF(( DBG_CONTEXT,
" AtqFreeContextToCache(%08x, %d, sock=%08x)\n",
pContext, GetLastError(), pContext->hAsyncIO));
I_AtqFreeContextToCache( pContext, TRUE );
continue;
}
//
// Shutdown may close the socket from underneath us so don't
// assert, just warn.
//
if ( pContext->SockState != ATQ_SOCK_LISTENING ) {
DBGPRINTF(( DBG_CONTEXT,
"[AtqPoolThread] Warning-Socket not listening\n"
));
}
pContext->SockState = ATQ_SOCK_CONNECTED;
//
// Remove the context from the pending list and put
// it on the active list
//
ATQ_ASSERT( pContext->ContextList != NULL);
pContext->ContextList->MoveToActiveList( &pContext->ListEntry);
//
// Set the connection indicated flag. After we return from
// the connection completion routine we assume it's
// safe to call the IO completion routine
// (or the connection indication routine should do cleanup
// and never issue an IO request). This is primarily for
// the timeout thread.
//
pContext->fConnectionIndicated = TRUE;
ATQ_ASSERT( pContext->pfnConnComp != NULL );
pContext->pfnConnComp(
pContext,
cbWritten,
NO_ERROR,
lpo
);
} else {
//
// Not a connection completion indication
//
if ( pContext->pfnCompletion ) {
//
// If an error occurred on a TransmitFile (or other IO),
// set the state to connected so the socket will get
// closed on cleanup
//
if ( !fRet && pContext->SockState == ATQ_SOCK_UNCONNECTED )
{
pContext->SockState = ATQ_SOCK_CONNECTED;
}
pContext->pfnCompletion(
pContext->ClientContext,
cbWritten,
fRet ? NO_ERROR : GetLastError(),
lpo
);
}
}
} else {
//
// don't kill the initial thread
//
if ( ((DWORD)param == ATQ_INITIAL_THREAD) && !g_fShutdown ) {
continue;
}
//
// An error occurred. Either the thread timed out, the handle
// is going away or something bad happened. Let the thread exit.
//
returnValue = GetLastError();
break;
}
} // for
if ( InterlockedDecrement( (PLONG)&g_cThreads ) == 0 ) {
//
// Wake up ATQTerminate()
//
SetEvent( g_hShutdownEvent );
}
return returnValue;
} // AtqPoolThread
BOOL
I_AtqCheckThreadStatus(
PVOID Context
)
/*++
Routine Description:
This routine makes sure there is at least one thread in
the thread pool. We're fast and loose so a couple of extra
threads may be created.
Arguments:
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
--*/
{
BOOL fRet = TRUE;
//
// If no threads are available, kick a new one off up to the limit
//
// WE NEED TO CHANGE THE CONDITIONS FOR STARTING ANOTHER THREAD
// IT SHOULD NOT BE VERY EASY TO START A THREAD ....
//
if ( (g_cAvailableThreads == 0) &&
(g_cThreads < g_cMaxThreads) &&
(g_cThreads < g_cMaxThreadLimit) ) {
HANDLE hThread;
DWORD dwThreadID;
InterlockedIncrement( (PLONG)&g_cThreads );
hThread = CreateThread( NULL,
0,
(LPTHREAD_START_ROUTINE)AtqPoolThread,
Context,
0,
&dwThreadID );
if ( hThread ) {
CloseHandle( hThread ); // Free system resources
} else {
//
// We fail if there are no threads running
//
if ( InterlockedDecrement( (PLONG)&g_cThreads ) == 0) {
ATQ_PRINTF((buff,
"AtqCheckThread: Cannot create ATQ threads\n"));
fRet = FALSE;
}
}
}
return fRet;
} // I_AtqCheckThreadStatus()
/************************************************************
* Functions to Add/Delete Atq Contexts
************************************************************/
BOOL
I_AtqAddAsyncHandle(
IN OUT PATQ_CONT * ppatqContext,
PVOID ClientContext,
ATQ_COMPLETION pfnCompletion,
DWORD TimeOut,
HANDLE hAsyncIO
)
/*++
Description:
This functio adds creates a new NON-AcceptEx() based Atq Context,
and includes it in proper lists fo ATQ Context management.
Note:
The client should call this after the IO handle is openned
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.
--*/
{
BOOL fReturn = TRUE;
ATQ_ASSERT( ppatqContext != NULL);
ATQ_ASSERT( ClientContext != NULL);
*ppatqContext = NULL; // initialize
if ( g_fShutdown) {
SetLastError( ERROR_NOT_READY);
return (FALSE);
} else {
PATQ_CONT pContext;
//
// Note we take and release the lock here as we're
// optimizing for the reuseable context case
//
pContext = AtqAllocContextFromCache();
if ( pContext == NULL) {
return (FALSE);
}
//
// Fill out the context. We set NextTimeout to INFINITE
// so the timeout thread will ignore this entry until an IO
// request is made unless this is an AcceptEx socket, that means
// we're about to submit the IO.
//
CanonTimeout( &TimeOut );
I_InitDefaultAtqContext( pContext, pfnCompletion, TimeOut, hAsyncIO);
//
// These data members are used if we're doing AcceptEx processing
//
I_InitAtqContextSetBuffer( pContext, NULL, 0);
I_InitNonAcceptExContext(pContext, ClientContext);
*ppatqContext = pContext;
}
return (TRUE);
} // I_AtqAddAsyncHandle()
BOOL
I_AtqAddListenSocketToPort(
IN OUT PATQ_CONT * ppatqContext,
IN ATQ_COMPLETION pfnOnConnect,
IN ATQ_COMPLETION pfnCompletion,
IN SOCKET sListenSocket
)
/*++
Description:
This function creates a new AtqContext for the given ListenSocket.
It uses the listen socket as the AcceptEx() socket too for adding
the atq context to the completion port.
It assumes
TimeOut to be INFINITE, with no ListenInfo structure.
Arguments:
ppatqContext - pointer to location that will contain the atq context
on successful return.
pfnOnConnect - pointer to connection callback function
pfnIOCompletion - pointer to IO Completion callback
sListenSocket - socket used for "listen" as well as the async
socket for IO Completion Port
Returns:
TRUE on success
FALSE if there is a failure.
Note:
The caller should free the *ppatqContext if there is a failure.
--*/
{
BOOL fReturn = TRUE;
PATQ_CONT pContext;
ATQ_ASSERT( pfnAcceptEx != NULL); // only support AcceptEx() cases
*ppatqContext = NULL; // initialize
if ( g_fShutdown) {
SetLastError( ERROR_NOT_READY);
return (FALSE);
} else {
//
// Note we take and release the lock here as we're
// optimizing for the reuseable context case
//
pContext = AtqAllocContextFromCache();
if ( pContext == NULL) {
return (FALSE);
}
//
// Fill out the context.
// We set the TimeOut for this object to be ATQ_INFINITE,
// since we do not want any interference from the Timeout loop.
//
I_InitDefaultAtqContext( pContext, pfnCompletion,
ATQ_INFINITE, (HANDLE ) sListenSocket);
//
// These data members are used if we're doing AcceptEx processing
//
I_InitAtqContextSetBuffer( pContext, NULL, 0);
pContext->pfnConnComp = pfnOnConnect;
pContext->pListenInfo = NULL;
//
// We set NextTimeout to INFINITE
// so the timeout thread will ignore this entry until an IO
// request is made unless this is an AcceptEx socket, that means
// we're about to submit the IO.
ATQ_ASSERT( pfnAcceptEx != NULL && pfnOnConnect != NULL);
I_InitAcceptExContext(pContext, ATQ_INFINITE);
*ppatqContext = pContext;
}
fReturn = I_AddAtqContextToPort( pContext);
return (fReturn);
} // I_AtqAddListenSocketToPort()
BOOL
I_AtqAddAsyncHandleEx(
PACCEPTEX_LISTEN_INFO pListenInfo,
PATQ_CONT pContext
)
/*++
Routine Description:
Adds a non-AcceptEx style Atq Context to the port
It expects the caller to send a AtqContext with certain characteristics
1) pContext is not NULL
2) pContext->pvBuff & pContext->cbBuff have valid values
Even in the case of failure, caller should call AtqFreeContext() and
free the memory associated with this object.
Arguments:
pListenInfo - pointer to listen Info object for this context
pReusableAtq - Pointer to ATQ context to reuse or
NULL to allocate a new one
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
The caller should free the object on a failure.
--*/
{
ATQ_ASSERT( pfnAcceptEx != NULL); // only support AcceptEx() cases
ATQ_ASSERT( pListenInfo != NULL);
ATQ_ASSERT( pContext != NULL);
//
// Make sure that we are adding a AcceptEx() version of AtqContext
//
ATQ_ASSERT( pListenInfo->pfnOnConnect != NULL);
//
// Fill out the context. We set NextTimeout to INFINITE
// so the timeout thread will ignore this entry until an IO
// request is made unless this is an AcceptEx socket, that means
// we're about to submit the IO.
//
DWORD TimeOut = pListenInfo->csecTimeout;
CanonTimeout( &TimeOut );
I_InitDefaultAtqContext(pContext,
pListenInfo->pfnIOCompletion,
TimeOut,
pContext->hAsyncIO);
//
// These data members are used if we're doing AcceptEx processing
//
pContext->pfnConnComp = pListenInfo->pfnOnConnect;
//
// TBD: What is the circumstance in which pContext->pListenInfo!= NULL?
//
if ( pContext->pListenInfo == NULL ) {
InterlockedIncrement( (PLONG)&pListenInfo->cRef );
pContext->pListenInfo = pListenInfo;
}
I_InitAcceptExContext(pContext, AtqGetCurrentTick() + TimeOut);
return (TRUE);
} // I_AtqAddAsyncHandleEx()
BOOL
I_AtqAddAcceptExSocket(
IN PACCEPTEX_LISTEN_INFO pListenInfo,
IN PATQ_CONT patqContext
)
/*++
Routine Description:
Adds the AtqContext to the AcceptEx() waiters list,
after allocating a new socket, since patqContext->hAsyncIO = NULL.
Arguments:
pListenInfo - Information about this listenning socket
patqReusedContext - optional context to use
Return Value:
TRUE on success, FALSE on failure. If an AtqContext and buffer are passed
in to be reused, they will be freed by this routine on failure.
--*/
{
BOOL fAddToPort = FALSE;
BOOL fSuccess = TRUE;
DBG_ASSERT( patqContext != NULL);
//
// If this listen socket isn't accepting new connections, just return
//
if ( !pListenInfo->fAccepting ) {
SetLastError( ERROR_NOT_READY );
// do not remove from any lists
I_AtqFreeContextToCache( patqContext, FALSE );
return ( FALSE);
}
//
// Use the supplied socket if any.
// Otherwise create a new socket
//
if ( patqContext->hAsyncIO == NULL) {
HANDLE sAcceptSocket = (HANDLE) socket( PF_INET, SOCK_STREAM, 0 );
if ( (SOCKET ) sAcceptSocket == INVALID_SOCKET ) {
fSuccess = FALSE;
sAcceptSocket = NULL;
//
// Free up the socket, buffer and context
//
if ( sAcceptSocket != NULL ) {
ATQ_REQUIRE( !closesocket( (int) sAcceptSocket ));
ATQ_ASSERT( patqContext->hAsyncIO == NULL);
}
//
// no need to unlink from any list, since we did not add it to any
//
I_AtqFreeContextToCache( patqContext, FALSE );
} else {
//
// Setup the accept ex socket in the atq context.
//
patqContext->hAsyncIO = sAcceptSocket;
fAddToPort = TRUE;
DBG_ASSERT( fSuccess);
}
}
if ( fSuccess) {
DWORD cbRecvd;
if ( g_fShutdown) {
//
// no need to unlink from any list, since we did not add it to any
//
I_AtqFreeContextToCache( patqContext, FALSE );
SetLastError( ERROR_NOT_READY);
return (FALSE);
}
DBG_ASSERT( patqContext->hAsyncIO != NULL);
//
// 1. Call I_AtqAddAsyncHandleEx() to establish the links with
// proper AcceptEx & AtqContext processing lists.
//
// After 1, the atqcontext will be in the lists, so
// cleanup should remove the context from proper lists.
//
// 2. Add the socket to Completion Port (if new),
// i.e. if fAddToPort is true)
//
// 3. Submit the new socket to AcceptEx() so that it may be
// used for processing about the new connections.
//
fSuccess = (// 1.
I_AtqAddAsyncHandleEx(pListenInfo,
patqContext
)
&&
// 2.
( !fAddToPort || I_AddAtqContextToPort( patqContext))
&&
// 3.
(
pfnAcceptEx( (int) pListenInfo->sListenSocket,
(int) patqContext->hAsyncIO,
patqContext->pvBuff,
pListenInfo->cbInitialRecvSize,
MIN_SOCKADDR_SIZE,
MIN_SOCKADDR_SIZE,
&cbRecvd,
&patqContext->Overlapped )
||
(GetLastError() == ERROR_IO_PENDING)
)
);
if ( fSuccess) {
//
// We've successfully added this socket, increment the count
//
InterlockedIncrement( (PLONG)&pListenInfo->cSocketsAvail );
} else {
ATQ_PRINTF(( buff,
"[AtqAddAcceptExSocket] Reusing an old context (%08x)"
" failed; error %d, sAcceptSocket = %x, "
" pListenInfo = %lx\n",
patqContext,
GetLastError(),
patqContext->hAsyncIO,
pListenInfo ));
//
// This frees the buffer and socket contained in the context
// as well as unlinks the atq context from the appropriate lists.
//
I_AtqFreeContextToCache( patqContext, TRUE );
}
}
return ( fSuccess);
} // I_AtqAddAcceptExSocket()
BOOL
I_AtqPrepareAcceptExSockets(
IN ACCEPTEX_LISTEN_INFO * pListenInfo,
IN DWORD nSockets
)
/*++
Routine Description:
Prepare specified number of AcceptEx sockets for the given
ListenSocket in [pListenInfo]
Arguments:
pListenInfo - Information about this listenning socket
nSockets - number of AcceptEx() sockets to be created.
Return Value:
TRUE on success, FALSE on failure.
--*/
{
BOOL fReturn;
DWORD cbBuffer;
DWORD i;
if ( !pfnAcceptEx ) {
SetLastError( ERROR_NOT_SUPPORTED );
return FALSE;
}
//
// If this listen socket isn't accepting new connections, just return
//
if ( !pListenInfo->fAccepting ) {
SetLastError( ERROR_NOT_READY );
return(FALSE);
}
// calculate the buffer size
cbBuffer = pListenInfo->cbInitialRecvSize + 2* MIN_SOCKADDR_SIZE;
for ( fReturn = TRUE, i = 0 ; fReturn && i++ < nSockets; ) {
PVOID pvBuff;
PATQ_CONT patqContext;
//
// Alloc a buffer for receive data
//
pvBuff = LocalAlloc( LPTR, cbBuffer);
//
// Get the ATQ context now because we need its overlapped structure
//
patqContext = AtqAllocContextFromCache();
//
// Now check if allocations are valid and do proper cleanup on failure
//
if ( pvBuff == NULL || patqContext == NULL) {
if ( pvBuff ) {
LocalFree( pvBuff );
}
if ( patqContext ) {
I_AtqFreeContextToCache( patqContext, FALSE );
}
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
fReturn = FALSE;
} else {
//
// Add this socket to AtqContext lists & completion ports
// From now on the called function will take care of freeing up
// patqContext, if there is a failure.
//
I_InitAtqContextSetBuffer( patqContext, pvBuff, cbBuffer);
patqContext->hAsyncIO = NULL;
fReturn = I_AtqAddAcceptExSocket(pListenInfo, patqContext);
}
} // for
ATQ_PRINTF((buff,
"PrepareAcceptExSockets( ListenInfo[%08x], nSockets = %d)==>"
" avail = %d; Total Refs = %d.\n",
pListenInfo,
nSockets,
pListenInfo->cSocketsAvail,
pListenInfo->cRef
));
return ( fReturn);
} // I_AtqPrepareAcceptExSockets()
VOID
CanonTimeout(
PDWORD Timeout
)
{
if ( *Timeout == INFINITE ) {
*Timeout = ATQ_INFINITE;
return;
}
*Timeout = (*Timeout + ATQ_TIMEOUT_INTERVAL - 1) / ATQ_TIMEOUT_INTERVAL;
return;
} // CanonTimeout
VOID
UndoCanonTimeout(
PDWORD Timeout
)
{
if ( (*Timeout & ATQ_INFINITE) != 0 ) {
*Timeout = INFINITE;
return;
}
//
// Multiply this by the timeout interval
//
*Timeout = *Timeout * ATQ_TIMEOUT_INTERVAL;
return;
} // CanonTimeout
BOOL
I_TimeOutContext(
PATQ_CONT Context
)
/*++
Routine Description:
Does the actual timeout for a particular context.
Arguments:
Context - Pointer to the context to be timed out
Return value:
TRUE, if the completion routine was called
FALSE, otherwise
--*/
{
DWORD timeout;
//
// Call client after re-checking that this item
// really has timed out
//
// Fake timeout
//
if ( Context->TimeOut == ATQ_INFINITE ) {
Context->NextTimeout = ATQ_INFINITE;
return(FALSE);
}
//
// Was our timeout long enough?
//
timeout = Context->BytesSent/g_cbMinKbSec;
CanonTimeout( &timeout );
if ( timeout > Context->TimeOut ) {
Context->NextTimeout = AtqGetCurrentTick( ) + timeout;
return(FALSE);
}
//
// If this is on blocked list, remove it.
//
if ( Context->fBlocked) {
ATQ_REQUIRE( AbwRemoveFromBlockedList(Context));
}
//
// If we've already indicated this connection to the client,
// then we abort them by calling their IO completion routine
// and letting them cleanup. Otherwise we close the socket
// which will generally cause an IO aborted completion that
// we will cleanup. Note there is a window where we may
// close the socket out from under a client in their
// connection completion routine but that should be ok.
//
if ( Context->pfnCompletion &&
Context->fConnectionIndicated ) {
//
// TransmitFile socket state will be unconnected because
// we're expecting it to complete successfully. Reset the
// state so the socket gets cleaned up properly
//
if ( Context->SockState == ATQ_SOCK_UNCONNECTED ) {
Context->SockState = ATQ_SOCK_CONNECTED;
}
AcIncrement( CacAtqContextsTimedOut);
Context->pfnCompletion(
Context->ClientContext,
0,
ERROR_SEM_TIMEOUT,
NULL
);
//
// We can't touch any items on the list after notifying
// the client as the client may have re-entered
// and freed some items from the list
//
return(TRUE);
} else {
HANDLE hIO;
hIO = (HANDLE ) InterlockedExchange(
(LPLONG ) &Context->hAsyncIO,
(LONG ) NULL
);
DBGPRINTF(( DBG_CONTEXT,
"Timeout: closesocket(%d) Context=%08x\n",
hIO, Context));
closesocket( (SOCKET) hIO );
}
return(FALSE);
} // I_TimeOutContext
VOID
AtqProcessTimeoutOfRequests(
PATQ_CONTEXT_LISTHEAD ContextList
)
/*++
Routine Description:
Walks the list of Atq clients looking for any item that has timed out and
notifies the client if it has.
TimeOutScanID is used as a serial number to prevent evaluating the same
context twice. We start from the beginning of the list everytime we
notify a client an Atq context has timed out. We do this because the
client timeout processing may remove any number of Items from the
list (including the next couple of items in the list).
This routine also checks to make sure outstanding AcceptEx sockets
haven't been exhausted (if less then 25% available, adds some more).
--*/
{
PATQ_CONT pContext;
LIST_ENTRY * pentry;
LIST_ENTRY * pentryNext;
DWORD nextTimeout;
DWORD scanId;
DWORD newLatest = ATQ_INFINITE;
BOOL fRescan;
DWORD atqSyncTimeout;
//
// See if the latest one is timed-out
//
if ( ContextList->LatestTimeout > AtqGetCurrentTick( ) ) {
return;
}
scanId = AtqGetCurrentTick( );
//
// Scan the timeout list looking for items that have timed out
// and adjust the timeout values
//
ContextList->Lock( );
do {
fRescan = FALSE;
for ( pentry = ContextList->ActiveListHead.Flink;
pentry != &ContextList->ActiveListHead;
pentry = pentryNext ) {
pentryNext = pentry->Flink;
pContext = CONTAINING_RECORD(
pentry,
ATQ_CONTEXT,
ListEntry
);
if ( pContext->Signature != ATQ_SIGNATURE ) {
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE );
break;
}
//
// Ignore items we've already processed
//
if ( pContext->TimeOutScanID == scanId ) {
continue;
}
pContext->TimeOutScanID = scanId;
//
// The client specifies the IO doesn't timeout if
// INFINITE is in the TimeOut field of the ATQ context
// If we've timed out, then notify the client.
//
nextTimeout = pContext->NextTimeout;
if ( nextTimeout > AtqGetCurrentTick() ) {
if ( nextTimeout < newLatest ) {
newLatest = nextTimeout;
}
continue;
}
//
// If there is an IO which has popped up now,
// we have to do nothing. This code was added to protect catapult!
//
pContext->fInTimeout = TRUE;
atqSyncTimeout = InterlockedExchange( &pContext->lSyncTimeout,
AtqProcessingTimeout);
ATQ_ASSERT( AtqIdle != atqSyncTimeout);
if ( AtqProcessingIo != atqSyncTimeout) {
if ( I_TimeOutContext(pContext) ) {
// we are done checkin and processing timeouts.
// reset the fInTimeout flag
pContext->fInTimeout = FALSE;
fRescan = TRUE;
break;
}
} else {
AcIncrement( CacAtqProcWhenTimeout);
}
// we are done checkin and processing timeouts.
// reset the fInTimeout flag
DBGPRINTF(( DBG_CONTEXT, "Ending Timeout %08x\n", pContext));
pContext->fInTimeout = FALSE;
} // scan list
} while (fRescan);
ContextList->LatestTimeout = newLatest;
ContextList->Unlock( );
return;
} // AtqProcessTimeoutOfRequests
VOID
I_AtqProcessPendingListens(
IN PATQ_CONTEXT_LISTHEAD pContextList
)
/*++
Routine Description:
Walks the list of Pending accept ex and makes sure none has timed out.
Also checks to see if we need to allocate more AcceptEx sockets.
Arguments:
pContextList - pointer to ATQ_CONTEXT_LISTHEAD object
Returns:
None
--*/
{
BOOL fRescan;
PATQ_CONT pContext;
LIST_ENTRY * pentry;
LIST_ENTRY * pentryNext;
DWORD nextTimeout;
ACCEPTEX_LISTEN_INFO * pListenInfo;
DWORD scanId = AtqGetCurrentTick( );
//
// Check to make sure outstanding AcceptEx sockets
// haven't been exhausted (if less then 25% available, adds some more).
//
pContextList->Lock();
//
// Look through the listening sockets to make sure the AcceptEx sockets
// haven't been exhausted
//
do {
fRescan = FALSE;
for ( pentry = pContextList->PendingAcceptExListHead.Flink;
pentry != &pContextList->PendingAcceptExListHead;
pentry = pentryNext ) {
DWORD dwConnect;
int cbOptLen = sizeof( dwConnect );
pentryNext = pentry->Flink;
pContext = CONTAINING_RECORD(
pentry,
ATQ_CONTEXT,
ListEntry
);
if ( pContext->Signature != ATQ_SIGNATURE ) {
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE );
break;
}
//
// Ignore items we've already processed
//
if ( pContext->TimeOutScanID == scanId ) {
continue;
}
pContext->TimeOutScanID = scanId;
//
// TBD: Optimize calling getsockopt() based on
// current count in pListenInfo->cAvailDuringTimeOut
//
//
// If this is an AcceptEx socket, ignore it if we're
// not connected yet
//
if ( pContext->pListenInfo != NULL ) {
if ( getsockopt(
(SOCKET) pContext->hAsyncIO,
SOL_SOCKET,
SO_CONNECT_TIME,
(char *) &dwConnect,
&cbOptLen ) != SOCKET_ERROR ) {
if ( dwConnect == (DWORD) -1 ) {
//
// Ignore the "Listen" socket context
pContext->pListenInfo->cAvailDuringTimeOut++;
//
// Not connected, ignore timeout processing for this
// socket
//
pContext->NextTimeout =
AtqGetCurrentTick() + pContext->TimeOut;
continue;
}
}
}
//
// The client specifies the IO doesn't timeout if
// INFINITE is in the TimeOut field of the ATQ context
// If we've timed out, then notify the client.
//
nextTimeout = pContext->NextTimeout;
if ( nextTimeout > AtqGetCurrentTick() ) {
continue;
}
if ( I_TimeOutContext(pContext) ) {
fRescan = TRUE;
break;
}
} // scan list
} while (fRescan);
pContextList->Unlock();
return;
} // I_AtqProcessPendingListens()
VOID
I_AtqCheckListenInfos(VOID)
/*++
Description:
This function checks all the listen info objects and adds appropriate
number of accept ex sockets as necessary.
Arguments:
None
Returns:
None
--*/
{
LIST_ENTRY * pentry;
ACCEPTEX_LISTEN_INFO * pListenInfo;
AcquireLock( &AtqListenInfoLock);
for ( pentry = AtqListenInfoList.Flink;
pentry != &AtqListenInfoList;
pentry = pentry->Flink ) {
pListenInfo = CONTAINING_RECORD(
pentry,
ACCEPTEX_LISTEN_INFO,
ListEntry
);
ATQ_ASSERT( pListenInfo->Signature == ACCEPTEX_LISTEN_SIGN );
if ( pListenInfo->cAvailDuringTimeOut <
(pListenInfo->cNewIncrement >> 2) ) {
ATQ_PRINTF((buff,
"[Timeout] Adding additional acceptex sockets\n"));
AddOutstandingAcceptExSockets( pListenInfo );
}
pListenInfo->cAvailDuringTimeOut = 0;
} // for
ReleaseLock( &AtqListenInfoLock);
return;
} // I_AtqCheckListenInfos()
VOID
I_AtqTimeOutWorker(VOID)
/*++
Description:
This function handles timeout processing using the simple
clock algorithm, wherein partial set of lists are scanned
during each timeout processing call.
Arguments:
None
Returns:
None
--*/
{
if ( (AtqGetCurrentTick() & 0x1) == 0 ) {
AtqProcessTimeoutOfRequests( &AtqActiveContextList[0] );
AtqProcessTimeoutOfRequests( &AtqActiveContextList[1] );
I_AtqProcessPendingListens( &AtqActiveContextList[0]);
I_AtqProcessPendingListens( &AtqActiveContextList[1]);
} else {
AtqProcessTimeoutOfRequests( &AtqActiveContextList[2] );
AtqProcessTimeoutOfRequests( &AtqActiveContextList[3] );
I_AtqProcessPendingListens( &AtqActiveContextList[2]);
I_AtqProcessPendingListens( &AtqActiveContextList[3]);
I_AtqCheckListenInfos( );
}
return;
} // I_AtqTimeOutWorker()
DWORD
AtqTimeoutThread(
LPDWORD param
)
/*++
Routine Description:
This is the thread that checks for timeouts
Clients should not call AtqFreeContext in their timeout
processing (could deadlock on global critical section).
The thread assumes timeouts are rounded to ATQ_TIMEOUT_INTERVAL
In addition to timing out requests when necessary, the timeout thread
also performs the job of bandwidth calculation and tuning the bandwidth
throttle operation (which works on feedback mechanism).
At every sampling interval the thread is awoken and it updates
the bandwidth.
Arguments:
param - Unused
Return Value:
Thread return value (ignored)
--*/
{
DWORD Timeout;
DWORD msTimeInterval;
DWORD cSamplesForTimeout; // number of samples before normal timeout
// we require the sampling interval to be less than the request timeout
// interval. So that we may be able to use feedback mechanism effectively.
ATQ_ASSERT( ATQ_SAMPLE_INTERVAL_IN_SECS < ATQ_TIMEOUT_INTERVAL );
Timeout = ATQ_TIMEOUT_INTERVAL;
cSamplesForTimeout = 1; // only timeout matters
msTimeInterval = GetTickCount();
for(;;) {
DWORD dwErr = WaitForSingleObject(g_hTimeoutEvent,TimeToWait(Timeout));
if ( dwErr == WAIT_TIMEOUT ) {
InterlockedIncrement( (PLONG)&AtqCurrentTick );
//
// When there is work to do, we wakeup every x seconds to look at
// the list. That's what we need to do now.
//
ATQ_ASSERT( cSamplesForTimeout >= 1);
if ( g_fBandwidthThrottle) {
--cSamplesForTimeout;
msTimeInterval = GetTickCount() - msTimeInterval;
// Perform a sampling to update measured bandwidth +
// apply feedback policy
AbwUpdateBandwidth( msTimeInterval);
DBG_CODE(
DBGPRINTF(( DBG_CONTEXT, "Sample @%u: Bandwidth = %u\n",
GetTickCount(), g_dwMeasuredBw));
);
if ( cSamplesForTimeout != 0) {
// We have not reached timeout yet. Proceed to wait
continue;
}
// We had reached the timeout interval for requests.
// Examine and release requests.
ATQ_ASSERT( cSamplesForTimeout == 0);
// reset the count of samples before proceeding.
cSamplesForTimeout = NUM_SAMPLES_PER_TIMEOUT_INTERVAL;
} else {
cSamplesForTimeout = 1;
}
//
// We are at a Timeout Interval. Examine and timeout requests.
//
I_AtqTimeOutWorker();
} else if ( dwErr == WAIT_OBJECT_0 ) {
if (g_fShutdown) {
return 0;
}
ATQ_PRINTF((buff,"[Timeout] Event unexpectedly signalled\n"));
} else {
//
// Somebody must have closed the event, time to leave
//
break;
}
}
return 0;
} // AtqTimeoutThread
VOID
WINAPI
I_AtqTimeoutCompletion(
IN PVOID Context
)
/*++
Routine Description:
Callback routine for the scheduled version of the timeout thread.
Arguments:
Context - Context returned by the scheduler thread.
Return Value:
none.
--*/
{
DWORD cookie;
g_hTimeoutThread = 0;
InterlockedIncrement( (PLONG)&AtqCurrentTick );
//
// We are at a Timeout Interval. Examine and timeout requests.
//
I_AtqTimeOutWorker();
if ( g_fShutdown ) {
ATQ_ASSERT( g_hTimeoutThread == 0);
ATQ_PRINTF((buff,
"Completion: Detected a shutdown when scheduling timeout\n"));
} else {
//
// Schedule ourself for the next round
//
g_hTimeoutThread =
(HANDLE)ScheduleWorkItem(
(PFN_SCHED_CALLBACK)I_AtqTimeoutCompletion,
Context,
TimeToWait(ATQ_TIMEOUT_INTERVAL)
);
if ( g_hTimeoutThread == (HANDLE)0 ) {
ATQ_PRINTF((buff,
"Completion: Error %d scheduling timeout\n",GetLastError()));
}
}
return;
} // I_AtqTimeoutCompletion
BOOL
I_AtqCreateTimeoutThread(
IN PVOID Context
)
/*++
Routine Description:
Starts the timeout process. If this is a server, it creates a timeout
thread, if not, it uses the scheduler to schedule the timeout.
Arguments:
Context - Context passed to the thread creation or scheduler thread.
Return Value:
TRUE, if ok
FALSE, otherwise
--*/
{
//
// Create the timeout event and kickoff the timeout thread
//
g_hTimeoutEvent = CreateEvent( NULL,
FALSE, // Auto reset
FALSE, // Not signalled
NULL
);
if ( g_hTimeoutEvent == NULL ) {
return(FALSE);
}
//
// Create an actual thread if a server, if not, schedule it
//
if ( TsIsNtServer() ) {
DWORD dwThreadID;
g_hTimeoutThread = CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)AtqTimeoutThread,
Context,
0,
&dwThreadID
);
if ( g_hTimeoutThread == NULL ) {
CloseHandle( g_hTimeoutEvent );
g_hTimeoutEvent = NULL;
ATQ_PRINTF((buff,
"Error %d creating timeout thread\n",GetLastError()));
return(FALSE);
}
} else {
//
// Make sure scheduler has been initialized
//
SchedulerInitialize( );
g_hTimeoutThread = (HANDLE)ScheduleWorkItem(
(PFN_SCHED_CALLBACK)I_AtqTimeoutCompletion,
Context,
TimeToWait(ATQ_TIMEOUT_INTERVAL)
);
if ( g_hTimeoutThread == 0 ) {
CloseHandle( g_hTimeoutEvent );
g_hTimeoutEvent = NULL;
ATQ_PRINTF((buff,
"Error %d scheduling timeout\n",GetLastError()));
return(FALSE);
}
}
return(TRUE);
} // I_AtqCreateTimeoutThread