/*++

   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 ***********************/