1055 lines
28 KiB
C++
1055 lines
28 KiB
C++
/*++
|
||
|
||
Copyright (c) 1995-1997 Microsoft Corporation
|
||
|
||
Module Name :
|
||
timeout.cxx
|
||
|
||
Abstract:
|
||
This module contains code for timeout processing of ATQ contexts
|
||
|
||
Author:
|
||
|
||
Murali R. Krishnan ( MuraliK ) 16-July-1997
|
||
|
||
Environment:
|
||
Win32 - User Mode
|
||
|
||
Project:
|
||
Internet Server - Asynchronous Thread Queue Module
|
||
|
||
Functions Exported:
|
||
|
||
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
|
||
/************************************************************
|
||
* Include Headers
|
||
************************************************************/
|
||
|
||
#include "isatq.hxx"
|
||
|
||
|
||
/************************************************************
|
||
* Globals
|
||
************************************************************/
|
||
|
||
DWORD g_dwTimeoutCookie = 0; // Scheduler Cookie for timeout processing
|
||
|
||
DWORD g_AtqCurrentTick = 1;
|
||
|
||
DWORD g_dwTimeout = ATQ_TIMEOUT_INTERVAL; // active timeout value
|
||
|
||
/************************************************************
|
||
* Functions
|
||
************************************************************/
|
||
|
||
|
||
|
||
BOOL
|
||
I_TimeOutContext(
|
||
PATQ_CONT pAtqContext
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function does the actual timeout for a particular context.
|
||
Note: The Context list lock is held while processing this function
|
||
|
||
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 ( pAtqContext->TimeOut == ATQ_INFINITE ) {
|
||
pAtqContext->NextTimeout = ATQ_INFINITE;
|
||
return(FALSE);
|
||
}
|
||
|
||
//
|
||
// Was our timeout long enough?
|
||
//
|
||
|
||
// NYI: Optimize: CanonTimeout should be called only once per IO submitted
|
||
timeout = CanonTimeout( pAtqContext->BytesSent/g_cbMinKbSec);
|
||
if ( timeout > pAtqContext->TimeOut ) {
|
||
|
||
//
|
||
// Reset the Timeout value based on the bytes to be sent
|
||
// as well as update the time when this pAtqContext be timedout
|
||
//
|
||
|
||
pAtqContext->TimeOut = timeout;
|
||
pAtqContext->NextTimeout = AtqGetCurrentTick( ) + timeout;
|
||
return(FALSE);
|
||
}
|
||
|
||
//
|
||
// If this is on blocked list, remove it.
|
||
//
|
||
|
||
if ( pAtqContext->IsBlocked()) {
|
||
PBANDWIDTH_INFO pBandwidthInfo = pAtqContext->m_pBandwidthInfo;
|
||
ATQ_ASSERT( pBandwidthInfo != NULL );
|
||
ATQ_REQUIRE( pBandwidthInfo->RemoveFromBlockedList(pAtqContext));
|
||
}
|
||
|
||
//
|
||
// 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 ( pAtqContext->pfnCompletion &&
|
||
pAtqContext->IsFlag( ACF_CONN_INDICATED)) {
|
||
|
||
//
|
||
// 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 ( pAtqContext->IsState( ACS_SOCK_UNCONNECTED) ) {
|
||
pAtqContext->MoveState( ACS_SOCK_CONNECTED);
|
||
}
|
||
|
||
AcIncrement( CacAtqContextsTimedOut);
|
||
|
||
pAtqContext->NextTimeout = ATQ_INFINITE;
|
||
|
||
pAtqContext->IOCompletion( 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 ) InterlockedExchangePointer(
|
||
(PVOID *)&pAtqContext->hAsyncIO,
|
||
NULL
|
||
);
|
||
|
||
IF_DEBUG( TIMEOUT) {
|
||
ATQ_PRINTF(( DBG_CONTEXT,
|
||
"Timeout: closesocket(%d) Context=%08x\n",
|
||
hIO, pAtqContext));
|
||
}
|
||
closesocket( HANDLE_TO_SOCKET(hIO) );
|
||
}
|
||
|
||
return(FALSE); // we can touch the items on current list.
|
||
|
||
} // 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).
|
||
|
||
--*/
|
||
{
|
||
DWORD newLatest = ATQ_INFINITE;
|
||
BOOL fRescan;
|
||
|
||
//
|
||
// See if the latest one is timed-out
|
||
//
|
||
|
||
if ( ContextList->LatestTimeout > AtqGetCurrentTick( ) ) {
|
||
|
||
return;
|
||
}
|
||
|
||
// set the latest timeout in the context list,
|
||
// to avoid races with IO being started.
|
||
ContextList->LatestTimeout = ATQ_INFINITE;
|
||
|
||
//
|
||
// Scan the timeout list looking for items that have timed out
|
||
// and adjust the timeout values
|
||
//
|
||
|
||
do {
|
||
|
||
LIST_ENTRY * pentry;
|
||
LIST_ENTRY * pentryNext;
|
||
DWORD scanId = AtqGetCurrentTick( );
|
||
|
||
ContextList->Lock( );
|
||
|
||
fRescan = FALSE;
|
||
|
||
for ( pentry = ContextList->ActiveListHead.Flink;
|
||
pentry != &ContextList->ActiveListHead;
|
||
pentry = pentryNext ) {
|
||
|
||
PATQ_CONT pContext;
|
||
|
||
pentryNext = pentry->Flink;
|
||
|
||
pContext = CONTAINING_RECORD(
|
||
pentry,
|
||
ATQ_CONTEXT,
|
||
m_leTimeout
|
||
);
|
||
|
||
if ( pContext->Signature != ATQ_CONTEXT_SIGNATURE ) {
|
||
ATQ_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Ignore items we've already processed
|
||
//
|
||
|
||
if ( pContext->TimeOutScanID == scanId ) {
|
||
continue;
|
||
}
|
||
|
||
pContext->TimeOutScanID = scanId;
|
||
|
||
//
|
||
// If there is an IO which has popped up now,
|
||
// we have to do nothing. This code was added to protect catapult!
|
||
//
|
||
pContext->SetFlag( ACF_IN_TIMEOUT);
|
||
|
||
if ( !pContext->lSyncTimeout) {
|
||
|
||
// no body is using this context. Check and synchronize
|
||
// the timeout state.
|
||
|
||
//
|
||
// 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.
|
||
//
|
||
|
||
DWORD nextTimeout = pContext->NextTimeout;
|
||
if ( nextTimeout > AtqGetCurrentTick() ) {
|
||
|
||
|
||
// pick up the latest "low" value for
|
||
// firing next timeout thread
|
||
if ( nextTimeout < newLatest ) {
|
||
newLatest = nextTimeout;
|
||
}
|
||
} else if ( I_TimeOutContext(pContext) ) {
|
||
|
||
// we are done checking and processing timeout.
|
||
// reset the In Timeout flag
|
||
pContext->ResetFlag( ACF_IN_TIMEOUT);
|
||
fRescan = TRUE;
|
||
break;
|
||
} else {
|
||
|
||
//
|
||
// It is possible that the timeout got reset
|
||
// Check for the latest "low" value
|
||
//
|
||
nextTimeout = pContext->NextTimeout;
|
||
if ( nextTimeout < newLatest ) {
|
||
newLatest = nextTimeout;
|
||
}
|
||
}
|
||
|
||
} else {
|
||
AcIncrement( CacAtqProcWhenTimeout);
|
||
}
|
||
|
||
// we are done checkin and processing timeouts.
|
||
// reset the In Timeout flag
|
||
pContext->ResetFlag( ACF_IN_TIMEOUT);
|
||
|
||
} // scan list
|
||
|
||
// let other system threads also run happily for a while
|
||
ContextList->Unlock( );
|
||
|
||
} while (fRescan);
|
||
|
||
if ( newLatest != ATQ_INFINITE) {
|
||
// We picked up the latest timeout. store it.
|
||
ContextList->LatestTimeout = newLatest;
|
||
}
|
||
|
||
return;
|
||
|
||
} // AtqProcessTimeoutOfRequests
|
||
|
||
|
||
|
||
|
||
//
|
||
// ACCEPT_EX_TIMEOUT_STATS collects statistics for the
|
||
// timeout processing in the Pending AcceptEx List//
|
||
//
|
||
struct ACCEPT_EX_TIMEOUT_STATS {
|
||
|
||
DWORD m_nScanned;
|
||
DWORD m_nTimedOut;
|
||
DWORD m_nSkipped;
|
||
DWORD m_nConnected;
|
||
DWORD m_nNotConnected;
|
||
};
|
||
|
||
|
||
BOOL
|
||
I_TimeOutPendingAcceptExContext(
|
||
PATQ_CONT pAtqContext
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function does the actual timeout for a pending AcceptEx context.
|
||
Note: The Context list lock is held while processing this function
|
||
|
||
Arguments:
|
||
|
||
pAtqContext - Pointer to the context to be timed out
|
||
|
||
Return value:
|
||
|
||
TRUE, if a tmieout operation was conducted.
|
||
FALSE, otherwise
|
||
|
||
--*/
|
||
{
|
||
DBG_ASSERT( pAtqContext != NULL);
|
||
|
||
//
|
||
// in the shutdown case it is possible that someone closed this socket already
|
||
// so don't worry about it.
|
||
//
|
||
if ( pAtqContext->hAsyncIO == NULL ) {
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// Validate our assumptions about this Pending AcceptEx Context
|
||
// there is an endpoint => AcceptEx context
|
||
DBG_ASSERT( pAtqContext->pEndpoint != NULL);
|
||
DBG_ASSERT( pAtqContext->IsState( ACS_SOCK_LISTENING));
|
||
DBG_ASSERT( !pAtqContext->IsFlag( ACF_CONN_INDICATED));
|
||
DBG_ASSERT( pAtqContext->TimeOut != ATQ_INFINITE);
|
||
|
||
|
||
//
|
||
// We will obtain the socket handle stored inside the AcceptEx Context
|
||
// and free up the context.
|
||
// Warning:
|
||
// The AcceptEx socket did not have a connection when this function
|
||
// was called. However now between the time when the state was checked
|
||
// and the time this timeout operation completes, it is possible that
|
||
// a new connection is bound to this AcceptEx context => we can get IO completion.
|
||
// I need to handle this case
|
||
//
|
||
|
||
HANDLE hIO;
|
||
|
||
hIO = (HANDLE ) InterlockedExchangePointer(
|
||
(PVOID *)&pAtqContext->hAsyncIO,
|
||
NULL
|
||
);
|
||
|
||
IF_DEBUG( TIMEOUT) {
|
||
ATQ_PRINTF(( DBG_CONTEXT,
|
||
"TimeoutPendingAcceptExContext(%08x): closesocket(%d)\n",
|
||
pAtqContext, hIO));
|
||
}
|
||
|
||
closesocket( HANDLE_TO_SOCKET(hIO) );
|
||
|
||
return ( TRUE);
|
||
} // I_TimeOutPendingAcceptExContext()
|
||
|
||
|
||
BOOL
|
||
I_IsTimeoutForAcceptExContext(
|
||
IN OUT PATQ_CONT pAtqContext,
|
||
IN OUT ACCEPT_EX_TIMEOUT_STATS * pAetStats,
|
||
OUT BOOL * pfIsConnected
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function checks to see if timeout operation has to be performed
|
||
for a given AtqContext. It bases the decision on the a variety of
|
||
details maintained in Atq Context and the Endpoint.
|
||
Note: The Context list lock is held while processing this function
|
||
|
||
Arguments:
|
||
|
||
pAtqContext - Pointer to the context to be timed out
|
||
pAetStats - pointer to AcceptEx Timeout Statistics structure
|
||
pfIsConnected - Set to TRUE if socket is connected to, but we're still
|
||
waiting for data. Such contexts are prime candidated
|
||
to be forcibly timed out by backlog monitor
|
||
|
||
Return value:
|
||
|
||
TRUE, if a tmieout operation has to be conducted.
|
||
FALSE, when no timeout is required.
|
||
|
||
--*/
|
||
{
|
||
DBG_ASSERT( pAtqContext);
|
||
DBG_ASSERT( pAetStats);
|
||
|
||
PATQ_ENDPOINT pEndpoint;
|
||
pEndpoint = pAtqContext->pEndpoint;
|
||
|
||
*pfIsConnected = FALSE;
|
||
|
||
if ( pEndpoint != NULL) {
|
||
|
||
//
|
||
// We will use getsockopt() to query the connection status
|
||
// for the socket inside the Atq context.
|
||
// If Socket is not connected => leave it in the pool
|
||
// If Socket is connected and waiting for receive operation =>
|
||
// do timeout processing
|
||
//
|
||
// The goal is to maintain a pool of sockets in listening state
|
||
// so that any new connection will be picked up quickly.
|
||
//
|
||
// getsockopt() is a very costly function.
|
||
// We check to see if we have enough sockets available
|
||
// for an endpoint. If they are, then we bypass calling getsockopt
|
||
// "enough" is defined as # of available sockets is at least
|
||
// 25% of the total # of accept ex sockets outstanding.
|
||
// Optimize calling getsockopt() based on
|
||
// current count in pEndpoint->nAvailDuringTimeOut
|
||
//
|
||
|
||
if ( pEndpoint->nAvailDuringTimeOut >
|
||
( pEndpoint->nAcceptExOutstanding >> 2)
|
||
) {
|
||
|
||
// Already enough Contexts are available.
|
||
// Do nothing
|
||
pAetStats->m_nSkipped++;
|
||
|
||
return (FALSE); // Do not timeout
|
||
}
|
||
|
||
DWORD dwConnect;
|
||
int cbOptLen = sizeof( dwConnect );
|
||
|
||
//
|
||
// Query the socket layer if the current socket has a valid connection
|
||
// An AcceptEx socket can be connected and waiting for new request to
|
||
// be read. If we are in such state we should not blow away context.
|
||
//
|
||
if ( getsockopt(HANDLE_TO_SOCKET(pAtqContext->hAsyncIO),
|
||
SOL_SOCKET,
|
||
SO_CONNECT_TIME,
|
||
(char *) &dwConnect,
|
||
&cbOptLen ) != SOCKET_ERROR
|
||
) {
|
||
|
||
//
|
||
// A return value of 0xFFFFFFFF indicates that the given
|
||
// AcceptEx socket is not connected yet.
|
||
// Otherwise the socket is connected and is probably wating
|
||
// for request to be read or maybe a completion is already
|
||
// on its way.
|
||
//
|
||
|
||
if ( dwConnect == (DWORD) 0xFFFFFFFF ) {
|
||
|
||
//
|
||
// Ignore the "Listen" socket context
|
||
//
|
||
|
||
pAetStats->m_nNotConnected++;
|
||
|
||
DBG_ASSERT( NULL != pEndpoint);
|
||
pEndpoint->nAvailDuringTimeOut++;
|
||
|
||
// Update timeout values to give a breather interval
|
||
pAtqContext->NextTimeout =
|
||
AtqGetCurrentTick() + pAtqContext->TimeOut;
|
||
|
||
return ( FALSE); // Do not timeout
|
||
}
|
||
else if ( !pAtqContext->IsFlag(ACF_WINSOCK_CONNECTED) ) {
|
||
|
||
*pfIsConnected = TRUE;
|
||
|
||
//
|
||
// Mark that this context has connection indicated.
|
||
// If this context waits around in connected state for
|
||
// long-time we need to blow the context away.
|
||
//
|
||
|
||
pAetStats->m_nConnected++;
|
||
|
||
// Update timeout values to give a breather interval
|
||
pAtqContext->NextTimeout =
|
||
AtqGetCurrentTick() + pAtqContext->TimeOut;
|
||
|
||
pAtqContext->SetFlag(ACF_WINSOCK_CONNECTED);
|
||
|
||
return (FALSE); // do not timeout now
|
||
}
|
||
}
|
||
} // if Endpoint exists
|
||
|
||
return (TRUE); // yes timeout this context
|
||
} // I_IsTimeoutForAcceptExContext()
|
||
|
||
|
||
VOID
|
||
I_AtqProcessPendingListens(
|
||
IN PATQ_CONTEXT_LISTHEAD pContextList,
|
||
IN PATQ_ENDPOINT pEndpoint,
|
||
OUT PDWORD pcForced
|
||
)
|
||
/*++
|
||
|
||
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
|
||
pEndpoint - pointer to ATQ_ENDPOINT object. If set, then only those
|
||
ATQ_CONTEXTs whose endpoint matches will be timed out.
|
||
If pEndpoint=NULL, then all ATQ_CONTEXTs will be timed out.
|
||
pcForced - If pEndpoint!=NULL, then this is set to # of forced sockets
|
||
|
||
Returns:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
BOOL fRescan;
|
||
BOOL fForceTimeout = ( pEndpoint != NULL );
|
||
BOOL fIsConnected = FALSE;
|
||
|
||
if ( fForceTimeout )
|
||
{
|
||
*pcForced = 0;
|
||
}
|
||
|
||
ACCEPT_EX_TIMEOUT_STATS AetStats;
|
||
|
||
//
|
||
// Initialize Statistics block
|
||
//
|
||
AetStats.m_nScanned = 0;
|
||
AetStats.m_nTimedOut = 0;
|
||
AetStats.m_nSkipped = 0;
|
||
AetStats.m_nConnected = 0;
|
||
AetStats.m_nNotConnected = 0;
|
||
|
||
//
|
||
// Look through the listening sockets to make sure the AcceptEx sockets
|
||
// haven't been exhausted
|
||
//
|
||
|
||
do {
|
||
|
||
LIST_ENTRY * pentry;
|
||
LIST_ENTRY * pentryNext;
|
||
DWORD scanId = AtqGetCurrentTick( );
|
||
|
||
fRescan = FALSE;
|
||
|
||
pContextList->Lock();
|
||
|
||
for ( pentry = pContextList->PendingAcceptExListHead.Flink;
|
||
pentry != &pContextList->PendingAcceptExListHead;
|
||
pentry = pentryNext ) {
|
||
|
||
PATQ_CONT pContext;
|
||
|
||
pentryNext = pentry->Flink;
|
||
pContext = CONTAINING_RECORD(
|
||
pentry,
|
||
ATQ_CONTEXT,
|
||
m_leTimeout
|
||
);
|
||
|
||
if ( pContext->Signature != ATQ_CONTEXT_SIGNATURE ) {
|
||
DBG_ASSERT( pContext->Signature == ATQ_CONTEXT_SIGNATURE );
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Use PATQ_ENDPOINT filter if necessary
|
||
//
|
||
|
||
if ( pEndpoint && ( pEndpoint != pContext->pEndpoint ) )
|
||
{
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Ignore items we've already processed
|
||
//
|
||
|
||
if ( pContext->TimeOutScanID == scanId ) {
|
||
continue;
|
||
}
|
||
|
||
AetStats.m_nScanned++;
|
||
pContext->TimeOutScanID = scanId;
|
||
|
||
//
|
||
// If the context has Timeout value smaller than the one in our global tick
|
||
// then examine if this context can be timedout
|
||
//
|
||
|
||
DBG_CODE( if ( pContext->IsAcceptExRootContext())
|
||
{
|
||
DBG_ASSERT( pContext->TimeOut == ATQ_INFINITE);
|
||
DBG_ASSERT( pContext->NextTimeout == ATQ_INFINITE);
|
||
}
|
||
);
|
||
|
||
if ( pContext->NextTimeout <= AtqGetCurrentTick() ||
|
||
fForceTimeout ) {
|
||
|
||
//
|
||
// Protect against the race with the normal IO completion
|
||
//
|
||
pContext->SetFlag( ACF_IN_TIMEOUT);
|
||
|
||
if ( !pContext->lSyncTimeout ) {
|
||
|
||
if ( !I_IsTimeoutForAcceptExContext( pContext,
|
||
&AetStats,
|
||
&fIsConnected )) {
|
||
|
||
if ( !fForceTimeout || !fIsConnected )
|
||
{
|
||
pContext->ResetFlag( ACF_IN_TIMEOUT);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if ( I_TimeOutPendingAcceptExContext(pContext)) {
|
||
AetStats.m_nTimedOut++;
|
||
pContext->ResetFlag(ACF_IN_TIMEOUT);
|
||
|
||
if ( fForceTimeout )
|
||
{
|
||
(*pcForced)++;
|
||
}
|
||
fRescan = TRUE;
|
||
break;
|
||
}
|
||
} // if (!pContext->lSyncTimeout)
|
||
|
||
pContext->ResetFlag( ACF_IN_TIMEOUT);
|
||
} // if the context's timeout value <= CurrentTick
|
||
else {
|
||
|
||
//
|
||
// Timeout value has not been reached. Skip this context
|
||
//
|
||
|
||
AetStats.m_nSkipped++;
|
||
}
|
||
} // scan list
|
||
|
||
pContextList->Unlock();
|
||
|
||
} while (fRescan);
|
||
|
||
IF_DEBUG( TIMEOUT) {
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"TimeoutPendingListens( CtxtList[%d], AtqTick=%d)\n"
|
||
" Contexts Scanned=%d, Skipped=%d, TimedOut=%d,"
|
||
" Connected=%d, NotConnected=%d\n",
|
||
pContextList - AtqActiveContextList, AtqGetCurrentTick(),
|
||
AetStats.m_nScanned, AetStats.m_nSkipped,
|
||
AetStats.m_nTimedOut, AetStats.m_nConnected,
|
||
AetStats.m_nNotConnected
|
||
));
|
||
}
|
||
|
||
# ifdef IIS_AUX_COUNTERS
|
||
g_AuxCounters[CacAtqPendingAcceptExScans] += AetStats.m_nScanned;
|
||
# endif // IIS_AUX_COUNTERS
|
||
|
||
return;
|
||
|
||
} // I_AtqProcessPendingListens()
|
||
|
||
|
||
|
||
|
||
VOID
|
||
I_AtqCheckEndpoints(
|
||
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;
|
||
PATQ_ENDPOINT pEndpoint;
|
||
|
||
AcquireLock( &AtqEndpointLock);
|
||
|
||
for ( pEntry = AtqEndpointList.Flink;
|
||
pEntry != &AtqEndpointList;
|
||
pEntry = pEntry->Flink ) {
|
||
|
||
pEndpoint = CONTAINING_RECORD(
|
||
pEntry,
|
||
ATQ_ENDPOINT,
|
||
ListEntry
|
||
);
|
||
|
||
DBG_ASSERT( pEndpoint->Signature == ATQ_ENDPOINT_SIGNATURE );
|
||
|
||
DBG_ASSERT( pEndpoint->nSocketsAvail >= 0);
|
||
|
||
//
|
||
// Check to make sure outstanding AcceptEx sockets
|
||
// haven't been exhausted (if less then 25% available, adds some more).
|
||
//
|
||
|
||
if ( ((DWORD ) pEndpoint->nSocketsAvail) <
|
||
(pEndpoint->nAcceptExOutstanding >> 2) ) {
|
||
|
||
IF_DEBUG( TIMEOUT ) {
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"[Timeout] Adding AcceptEx Contexts for EP=%08x; nAvail = %d;\n",
|
||
pEndpoint, pEndpoint->nSocketsAvail));
|
||
}
|
||
|
||
(VOID ) I_AtqPrepareAcceptExSockets(pEndpoint,
|
||
pEndpoint->nAcceptExOutstanding
|
||
);
|
||
}
|
||
|
||
//
|
||
// set to zero, so recount will be done during next timeout loop
|
||
//
|
||
|
||
pEndpoint->nAvailDuringTimeOut = 0;
|
||
}
|
||
|
||
ReleaseLock( &AtqEndpointLock);
|
||
|
||
return;
|
||
} // I_AtqCheckEndpoints
|
||
|
||
|
||
|
||
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
|
||
--*/
|
||
{
|
||
DWORD start;
|
||
PATQ_CONTEXT_LISTHEAD pContextList;
|
||
|
||
IF_DEBUG(TIMEOUT) {
|
||
DBGPRINTF((DBG_CONTEXT, "TimeoutWorker: entered\n"));
|
||
}
|
||
|
||
start = (AtqGetCurrentTick() & 0x1);
|
||
|
||
for ( pContextList = AtqActiveContextList + start;
|
||
pContextList < (AtqActiveContextList + g_dwNumContextLists) ;
|
||
pContextList += 2 ) {
|
||
|
||
IF_DEBUG(TIMEOUT) {
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"TimeoutWorker: Processing list[%d] = %08x\n",
|
||
(pContextList - AtqActiveContextList),
|
||
pContextList));
|
||
}
|
||
|
||
AtqProcessTimeoutOfRequests( pContextList );
|
||
I_AtqProcessPendingListens( pContextList, NULL, NULL );
|
||
} // for
|
||
|
||
if ( start != 0 ) {
|
||
I_AtqCheckEndpoints( );
|
||
}
|
||
|
||
return;
|
||
|
||
} // I_AtqTimeOutWorker()
|
||
|
||
|
||
|
||
|
||
VOID
|
||
WINAPI
|
||
I_AtqTimeoutCompletion(
|
||
IN PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Callback routine for the scheduled version of the timeout thread.
|
||
|
||
The callback 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 scheduled callback comes in and it updates
|
||
the bandwidth.
|
||
|
||
Arguments:
|
||
|
||
Context - Context returned by the scheduler thread.
|
||
|
||
Return Value:
|
||
|
||
none.
|
||
|
||
--*/
|
||
{
|
||
DWORD Timeout = ATQ_TIMEOUT_INTERVAL;
|
||
BOOL fDoContextTimeout = TRUE;
|
||
|
||
if ( g_fShutdown ) {
|
||
|
||
ATQ_PRINTF(( DBG_CONTEXT,
|
||
"Detected a shutdown while entering timeout callback\n"));
|
||
return;
|
||
}
|
||
|
||
InterlockedIncrement( (PLONG)&g_AtqCurrentTick );
|
||
|
||
//
|
||
// Perform necessary steps to handle Bandwidth throttling.
|
||
//
|
||
|
||
ATQ_ASSERT( BANDWIDTH_INFO::sm_cSamplesForTimeout >= 1);
|
||
|
||
IF_DEBUG(TIMEOUT) {
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"Timeout: BANDWIDTH_INFO::cSamplesForTimeout=%d\n",
|
||
BANDWIDTH_INFO::sm_cSamplesForTimeout ));
|
||
}
|
||
|
||
if ( BANDWIDTH_INFO::GlobalActive() ) {
|
||
|
||
--(BANDWIDTH_INFO::sm_cSamplesForTimeout);
|
||
|
||
// Perform a sampling to update measured bandwidth +
|
||
// apply feedback policy
|
||
|
||
BANDWIDTH_INFO::UpdateAllBandwidths();
|
||
|
||
Timeout = ATQ_SAMPLE_INTERVAL_IN_SECS;
|
||
if ( BANDWIDTH_INFO::sm_cSamplesForTimeout != 0) {
|
||
|
||
// We have not reached timeout yet. So skip context timeouts
|
||
|
||
fDoContextTimeout = FALSE;
|
||
} else {
|
||
|
||
// We had reached the timeout interval for requests.
|
||
// Examine and release requests.
|
||
ATQ_ASSERT( BANDWIDTH_INFO::sm_cSamplesForTimeout == 0);
|
||
|
||
// reset the count of samples before proceeding.
|
||
BANDWIDTH_INFO::sm_cSamplesForTimeout = NUM_SAMPLES_PER_TIMEOUT_INTERVAL;
|
||
}
|
||
} else {
|
||
BANDWIDTH_INFO::sm_cSamplesForTimeout = 1;
|
||
}
|
||
|
||
//
|
||
// We are at a Timeout Interval. Examine and timeout requests.
|
||
//
|
||
|
||
if ( fDoContextTimeout ) {
|
||
I_AtqTimeOutWorker();
|
||
}
|
||
|
||
if ( Timeout != g_dwTimeout) {
|
||
|
||
// the scheduled interval is different from this current interval
|
||
// Inidicate the changed timeout value to the scheduler
|
||
|
||
ScheduleAdjustTime( g_dwTimeoutCookie, TimeToWait(Timeout));
|
||
g_dwTimeout = Timeout;
|
||
}
|
||
|
||
return;
|
||
} // I_AtqTimeoutCompletion
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
I_AtqStartTimeoutProcessing(
|
||
IN PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Starts the timeout processing. It always uses the scheduler to schedule
|
||
a timeout operation.
|
||
|
||
Note: The scheduler should be initialized before getting to this function.
|
||
|
||
Arguments:
|
||
|
||
Context - Context passed to the thread creation or scheduler thread.
|
||
|
||
Return Value:
|
||
|
||
TRUE, if ok
|
||
FALSE, otherwise
|
||
|
||
--*/
|
||
{
|
||
ATQ_ASSERT( ATQ_SAMPLE_INTERVAL_IN_SECS < ATQ_TIMEOUT_INTERVAL );
|
||
|
||
if ( BANDWIDTH_INFO::GlobalEnabled() ) {
|
||
g_dwTimeout = ATQ_SAMPLE_INTERVAL_IN_SECS;
|
||
BANDWIDTH_INFO::sm_cSamplesForTimeout =
|
||
NUM_SAMPLES_PER_TIMEOUT_INTERVAL;
|
||
} else {
|
||
g_dwTimeout = ATQ_TIMEOUT_INTERVAL;
|
||
BANDWIDTH_INFO::sm_cSamplesForTimeout = 1;
|
||
}
|
||
|
||
g_dwTimeoutCookie =
|
||
ScheduleWorkItem(
|
||
I_AtqTimeoutCompletion,
|
||
Context,
|
||
TimeToWait(g_dwTimeout)
|
||
, TRUE // ask for periodic timeout
|
||
);
|
||
|
||
if ( g_dwTimeoutCookie == 0 ) {
|
||
|
||
ATQ_PRINTF(( DBG_CONTEXT,
|
||
"Error %d scheduling timeout\n",GetLastError()));
|
||
return(FALSE);
|
||
}
|
||
|
||
return(TRUE);
|
||
|
||
} // I_AtqStartTimeoutProcessing()
|
||
|
||
|
||
|
||
BOOL
|
||
I_AtqStopTimeoutProcessing(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Stops the timeout processing. It terminates the scheduled workitem and
|
||
cleans up any state.
|
||
|
||
Note: The scheduler should be terminated only after this call
|
||
|
||
Arguments:
|
||
|
||
Context - Context passed to the thread creation or scheduler thread.
|
||
|
||
Return Value:
|
||
|
||
TRUE, if ok
|
||
FALSE, otherwise
|
||
|
||
--*/
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT, "I_AtqStopTimeoutProcessing\n"));
|
||
|
||
if ( 0 != g_dwTimeoutCookie) {
|
||
DBG_REQUIRE( RemoveWorkItem( g_dwTimeoutCookie ));
|
||
g_dwTimeoutCookie = 0;
|
||
}
|
||
|
||
return ( TRUE);
|
||
|
||
} // I_AtqStopTimeoutProcessing()
|
||
|
||
|
||
/************************ End of File ***********************/
|