/*++ Copyright (c) 2001-2001 Microsoft Corporation Module Name: timeouts.cxx Abstract: Implement connection timeout quality-of-service (QoS) functionality. The following timers must be monitored during the lifetime of a connection: * Connection Timeout * Header Wait Timeout * Entity Body Receive Timeout * Response Processing Timeout * Minimum Bandwidth (implemented as a Timeout) When any one of these timeout values expires, the connection should be terminated. The timer information is maintained in a timeout info block, UL_TIMEOUT_INFO_ENTRY, as part of the UL_HTTP_CONNECTION object. A timer can be Set or Reset. Setting a timer calculates when the specific timer should expire, and updates the timeout info block. Resetting a timer turns a specific timer off. Both Setting and Resettnig a timer will cause the timeout block to be re-evaluated to find the least valued expiry time. // TODO: The timeout manager uses a Timer Wheel(c) technology, as used by NT's TCP/IP stack for monitoring TCB timeouts. We will reimplement and modify the logic they use. The linkage for the Timer Wheel(c) queues is provided in the timeout info block. // TODO: CONVERT TO USING Timer Wheel Ticks instead of SystemTime. There are three separate units of time: SystemTime (100ns intervals), Timer Wheel Ticks (SystemTime / slot interval length), and Timer Wheel Slot (Timer Wheel Ticks modulo the number of slots in the Timer Wheel). Author: Eric Stenson (EricSten) 24-Mar-2001 Revision History: This was originally implemented as the Connection Timeout Monitor. --*/ #include "precomp.h" #include "timeoutsp.h" // // Private globals. // #ifdef ALLOC_PRAGMA #pragma alloc_text( INIT, UlInitializeTimeoutMonitor ) #pragma alloc_text( PAGE, UlTerminateTimeoutMonitor ) #pragma alloc_text( PAGE, UlSetTimeoutMonitorInformation ) #pragma alloc_text( PAGE, UlpTimeoutMonitorWorker ) #pragma alloc_text( PAGE, UlSetPerSiteConnectionTimeoutValue ) #endif // ALLOC_PRAGMA #if 0 NOT PAGEABLE -- UlpSetTimeoutMonitorTimer NOT PAGEABLE -- UlpTimeoutMonitorDpcRoutine NOT PAGEABLE -- UlpTimeoutCheckExpiry NOT PAGEABLE -- UlpTimeoutInsertTimerWheelEntry NOT PAGEABLE -- UlTimeoutRemoveTimerWheelEntry NOT PAGEABLE -- UlInitializeConnectionTimerInfo NOT PAGEABLE -- UlSetConnectionTimer NOT PAGEABLE -- UlSetMinKBSecTimer NOT PAGEABLE -- UlResetConnectionTimer NOT PAGEABLE -- UlEvaluateTimerState #endif // 0 // // Connection Timeout Montior globals // LONG g_TimeoutMonitorInitialized = FALSE; KDPC g_TimeoutMonitorDpc; KTIMER g_TimeoutMonitorTimer; KEVENT g_TimeoutMonitorTerminationEvent; KEVENT g_TimeoutMonitorAddListEvent; UL_WORK_ITEM g_TimeoutMonitorWorkItem; // // Timeout constants // ULONG g_TM_MinKBSecDivisor; // Bytes/Sec LONGLONG g_TM_ConnectionTimeout; // 100ns ticks (Global...can be overriden) LONGLONG g_TM_HeaderWaitTimeout; // 100ns ticks // // NOTE: Must be in sync with the _CONNECTION_TIMEOUT_TIMERS enum // CHAR *g_aTimeoutTimerNames[] = { "ConnectionIdle", // TimerConnectionIdle "HeaderWait", // TimerHeaderWait "MinKBSec", // TimerMinKBSec "EntityBody", // TimerEntityBody "Response", // TimerResponse }; // // Timer Wheel(c) // static LIST_ENTRY g_TimerWheel[TIMER_WHEEL_SLOTS+1]; // TODO: alloc on its own page. static UL_SPIN_LOCK g_TimerWheelMutex; static USHORT g_TimerWheelCurrentSlot; // // Public functions. // /***************************************************************************++ Routine Description: Initializes the Timeout Monitor and kicks off the first polling interval Arguments: (none) --***************************************************************************/ VOID UlInitializeTimeoutMonitor( VOID ) { int i; LARGE_INTEGER Now; // // Sanity check. // PAGED_CODE(); UlTrace(TIMEOUTS, ( "http!UlInitializeTimeoutMonitor\n" )); ASSERT( FALSE == g_TimeoutMonitorInitialized ); // // Set default configuration information. // g_TM_ConnectionTimeout = 15 * 60 * C_NS_TICKS_PER_SEC; // 15 min g_TM_HeaderWaitTimeout = 15 * 60 * C_NS_TICKS_PER_SEC; // 15 min g_TM_MinKBSecDivisor = 0; // 0 == Disabled // // Init Timer Wheel(c) state // // // Set current slot // KeQuerySystemTime(&Now); g_TimerWheelCurrentSlot = UlpSystemTimeToTimerWheelSlot(Now.QuadPart); // // Init Timer Wheel(c) slots & mutex // ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); // InitializeListHead requirement for ( i = 0; i < TIMER_WHEEL_SLOTS ; i++ ) { InitializeListHead( &(g_TimerWheel[i]) ); } InitializeListHead( &(g_TimerWheel[TIMER_OFF_SLOT]) ); UlInitializeSpinLock( &(g_TimerWheelMutex), "TimeoutMonitor" ); // // Init DPC object & set DPC routine // KeInitializeDpc( &g_TimeoutMonitorDpc, // DPC object &UlpTimeoutMonitorDpcRoutine, // DPC routine NULL // context ); KeInitializeTimer( &g_TimeoutMonitorTimer ); // // Event to control rescheduling of the timeout monitor timer // KeInitializeEvent( &g_TimeoutMonitorAddListEvent, NotificationEvent, TRUE ); // // Initialize the termination event. // KeInitializeEvent( &g_TimeoutMonitorTerminationEvent, NotificationEvent, FALSE ); // // Init done! // InterlockedExchange( &g_TimeoutMonitorInitialized, TRUE ); // // Kick-off the first monitor sleep period // UlpSetTimeoutMonitorTimer(); } /***************************************************************************++ Routine Description: Terminate the Timeout Monitor, including any pending timer events. Arguments: (none) --***************************************************************************/ VOID UlTerminateTimeoutMonitor( VOID ) { // // Sanity check. // PAGED_CODE(); UlTrace(TIMEOUTS, ( "http!UlTerminateTimeoutMonitor\n" )); // // Clear the "initialized" flag. If the timeout monitor runs soon, // it will see this flag, set the termination event, and exit // quickly. // if ( TRUE == InterlockedCompareExchange( &g_TimeoutMonitorInitialized, FALSE, TRUE) ) { // // Cancel the timeout monitor timer. If it fails, the monitor // is running. Wait for it to complete. // if ( !KeCancelTimer( &g_TimeoutMonitorTimer ) ) { KeWaitForSingleObject( (PVOID)&g_TimeoutMonitorTerminationEvent, UserRequest, KernelMode, FALSE, NULL ); } } UlTrace(TIMEOUTS, ( "http!UlTerminateTimeoutMonitor: Done!\n" )); } // UlpTerminateTimeoutMonitor /***************************************************************************++ Routine Description: Sets the global Timeout Monitor configuration information Arguments: pInfo pointer to HTTP_CONTROL_CHANNEL_TIMEOUT_LIMIT structure --***************************************************************************/ VOID UlSetTimeoutMonitorInformation( IN PHTTP_CONTROL_CHANNEL_TIMEOUT_LIMIT pInfo ) { LONGLONG localValue; LONGLONG newValue; LONGLONG originalValue; // // Sanity check. // PAGED_CODE(); ASSERT( pInfo ); UlTrace(TIMEOUTS, ( "http!UlSetTimeoutMonitorInformation:\n" " ConnectionTimeout: %d\n" " HeaderWaitTimeout: %d\n" " MinFileKbSec: %d\n", pInfo->ConnectionTimeout, pInfo->HeaderWaitTimeout, pInfo->MinFileKbSec )); if ( pInfo->ConnectionTimeout ) { UlInterlockedExchange64( &g_TM_ConnectionTimeout, (LONGLONG)(pInfo->ConnectionTimeout * C_NS_TICKS_PER_SEC) ); } if ( pInfo->HeaderWaitTimeout ) { UlInterlockedExchange64( &g_TM_HeaderWaitTimeout, (LONGLONG)(pInfo->HeaderWaitTimeout * C_NS_TICKS_PER_SEC) ); } if ( pInfo->MinFileKbSec ) { InterlockedExchange( (PLONG)&g_TM_MinKBSecDivisor, pInfo->MinFileKbSec ); } } // UlSetTimeoutMonitorInformation /***************************************************************************++ Routine Description: Sets up a timer event to fire after the polling interval has expired. Arguments: (none) --***************************************************************************/ VOID UlpSetTimeoutMonitorTimer( VOID ) { LARGE_INTEGER TimeoutMonitorInterval; ASSERT( TRUE == g_TimeoutMonitorInitialized ); UlTrace(TIMEOUTS, ( "http!UlpSetTimeoutMonitorTimer\n" )); // // Don't want to execute this more often than few seconds. // In particular, do not want to execute this every 0 seconds, as the // machine will become completely unresponsive. // // // negative numbers mean relative time // TimeoutMonitorInterval.QuadPart = -DEFAULT_POLLING_INTERVAL; KeSetTimer( &g_TimeoutMonitorTimer, TimeoutMonitorInterval, &g_TimeoutMonitorDpc ); } /***************************************************************************++ Routine Description: Dispatch routine called by the timer event that queues up the Timeout Montior Arguments: (all ignored) --***************************************************************************/ VOID UlpTimeoutMonitorDpcRoutine( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) { if( g_TimeoutMonitorInitialized ) { // // Do that timeout monitor thang. // UL_QUEUE_WORK_ITEM( &g_TimeoutMonitorWorkItem, &UlpTimeoutMonitorWorker ); } } // UlpTimeoutMonitorDpcRoutine /***************************************************************************++ Routine Description: Timeout Monitor thread Arguments: pWorkItem (ignored) --***************************************************************************/ VOID UlpTimeoutMonitorWorker( IN PUL_WORK_ITEM pWorkItem ) { ULONG TimeoutMonitorEntriesSeen; LARGE_INTEGER Now; // // sanity check // PAGED_CODE(); UlTrace(TIMEOUTS, ( "http!UlpTimeoutMonitorWorker\n" )); // // Check for things to expire now. // UlpTimeoutCheckExpiry(); UlTrace(TIMEOUTS, ( "http!UlpTimeoutMonitorWorker: g_TimerWheelCurrentSlot is now %d\n", g_TimerWheelCurrentSlot )); if ( g_TimeoutMonitorInitialized ) { // // Reschedule ourselves // UlpSetTimeoutMonitorTimer(); } else { // // Signal shutdown event // KeSetEvent( &g_TimeoutMonitorTerminationEvent, 0, FALSE ); } } // UlpTimeoutMonitor /***************************************************************************++ Routine Description: Walks a given timeout watch list looking for items that should be expired Returns: Count of connections remaining on list (after all expired connections removed) Notes: Possible Issue: Since we use the system time to check to see if something should be expired, it's possible we will mis-expire some connections if the system time is set forward. Similarly, if the clock is set backward, we may not expire connections as expected. --***************************************************************************/ ULONG UlpTimeoutCheckExpiry( VOID ) { LARGE_INTEGER Now; KIRQL OldIrql; PLIST_ENTRY pEntry; PLIST_ENTRY pHead; PUL_HTTP_CONNECTION pHttpConn; PUL_TIMEOUT_INFO_ENTRY pInfo; LIST_ENTRY ZombieList; ULONG Entries; USHORT Limit; USHORT CurrentSlot; PAGED_CODE(); // // Init zombie list // InitializeListHead(&ZombieList); // // Get current time // KeQuerySystemTime(&Now); Limit = UlpSystemTimeToTimerWheelSlot( Now.QuadPart ); ASSERT( TIMER_OFF_SLOT != Limit ); // // Lock Timer Wheel(c) // UlAcquireSpinLock( &g_TimerWheelMutex, &OldIrql ); CurrentSlot = g_TimerWheelCurrentSlot; // // Walk the slots up until Limit // Entries = 0; while ( CurrentSlot != Limit ) { pHead = &(g_TimerWheel[CurrentSlot]); pEntry = pHead->Flink; ASSERT( pEntry ); // // Walk this slot's queue // while ( pEntry != pHead ) { pInfo = CONTAINING_RECORD( pEntry, UL_TIMEOUT_INFO_ENTRY, QueueEntry ); ASSERT( MmIsAddressValid(pInfo) ); pHttpConn = CONTAINING_RECORD( pInfo, UL_HTTP_CONNECTION, TimeoutInfo ); ASSERT( (pHttpConn != NULL) && \ (pHttpConn->Signature == UL_HTTP_CONNECTION_POOL_TAG) ); // // go to next node (in case we remove the current one from the list) // pEntry = pEntry->Flink; Entries++; if (0 == pHttpConn->RefCount) { // // If the ref-count has gone to zero, the httpconn will be // cleaned up soon; ignore this item and let the cleanup // do its job. // Entries--; continue; // inner while loop } // // See if we should move this entry to a different slot // if ( pInfo->SlotEntry != CurrentSlot ) { ASSERT( IS_VALID_TIMER_WHEEL_SLOT(pInfo->SlotEntry) ); ASSERT( pInfo->QueueEntry.Flink != NULL ); // // Move to correct slot // RemoveEntryList( &pInfo->QueueEntry ); InsertTailList( &(g_TimerWheel[pInfo->SlotEntry]), &pInfo->QueueEntry ); Entries--; continue; // inner while loop } // // See if we should expire this connection // UlAcquireSpinLockAtDpcLevel( &pInfo->Lock ); if ( pInfo->CurrentExpiry < Now.QuadPart ) { UlTrace(TIMEOUTS, ( "http!UlpTimeoutCheckExpiry: pInfo %p expired because %s timer\n", pInfo, g_aTimeoutTimerNames[pInfo->CurrentTimer] )); // // Expired. Remove entry from list & move to Zombie list // RemoveEntryList( &pInfo->QueueEntry ); pInfo->QueueEntry.Flink = NULL; InsertTailList( &ZombieList, &pInfo->ZombieEntry ); // // Add ref the pHttpConn to prevent it being killed before we // can kill it ourselves. (zombifying) // UL_REFERENCE_HTTP_CONNECTION(pHttpConn); } UlReleaseSpinLockFromDpcLevel( &pInfo->Lock ); } // Walk slot queue CurrentSlot = ((CurrentSlot + 1) % TIMER_WHEEL_SLOTS); } // ( CurrentSlot != Limit ) g_TimerWheelCurrentSlot = Limit; UlReleaseSpinLock( &g_TimerWheelMutex, OldIrql ); // // remove entries on zombie list // if ( !IsListEmpty(&ZombieList) ) { pEntry = ZombieList.Flink; while ( &ZombieList != pEntry ) { pInfo = CONTAINING_RECORD( pEntry, UL_TIMEOUT_INFO_ENTRY, ZombieEntry ); ASSERT( MmIsAddressValid(pInfo) ); pHttpConn = CONTAINING_RECORD( pInfo, UL_HTTP_CONNECTION, TimeoutInfo ); ASSERT( UL_IS_VALID_HTTP_CONNECTION(pHttpConn) ); pEntry = pEntry->Flink; UlCloseConnection(pHttpConn->pConnection, TRUE, NULL, NULL); // // Remove the reference we added when zombifying // UL_DEREFERENCE_HTTP_CONNECTION(pHttpConn); Entries--; } } return Entries; } // UlpTimeoutCheckExpiry // // New Timer Wheel(c) primatives // /***************************************************************************++ Routine Description: Arguments: --***************************************************************************/ VOID UlInitializeConnectionTimerInfo( PUL_TIMEOUT_INFO_ENTRY pInfo ) { LARGE_INTEGER Now; int i; ASSERT( TRUE == g_TimeoutMonitorInitialized ); // // Get current time // KeQuerySystemTime(&Now); // // Init Lock // UlInitializeSpinLock( &pInfo->Lock, "TimeoutInfoLock" ); // // Timer state // ASSERT( 0 == TimerConnectionIdle ); pInfo->Timers[TimerConnectionIdle] = TIMER_WHEEL_TICKS(Now.QuadPart + g_TM_ConnectionTimeout); for ( i = 1; i < TimerMaximumTimer; i++ ) { pInfo->Timers[i] = TIMER_OFF_TICK; } pInfo->CurrentTimer = TimerConnectionIdle; pInfo->CurrentExpiry = TIMER_WHEEL_TICKS(Now.QuadPart + g_TM_ConnectionTimeout); pInfo->MinKBSecSystemTime = TIMER_OFF_SYSTIME; pInfo->ConnectionTimeoutValue = g_TM_ConnectionTimeout; // // Wheel state // pInfo->SlotEntry = UlpTimerWheelTicksToTimerWheelSlot( pInfo->CurrentExpiry ); UlpTimeoutInsertTimerWheelEntry(pInfo); } // UlInitializeConnectionTimerInfo /***************************************************************************++ Routine Description: Arguments: --***************************************************************************/ VOID UlpTimeoutInsertTimerWheelEntry( PUL_TIMEOUT_INFO_ENTRY pInfo ) { KIRQL OldIrql; ASSERT( NULL != pInfo ); ASSERT( TRUE == g_TimeoutMonitorInitialized ); ASSERT( IS_VALID_TIMER_WHEEL_SLOT(pInfo->SlotEntry) ); UlTrace(TIMEOUTS, ( "http!UlTimeoutInsertTimerWheelEntry: pInfo %p Slot %d\n", pInfo, pInfo->SlotEntry )); UlAcquireSpinLock( &g_TimerWheelMutex, &OldIrql ); InsertTailList( &(g_TimerWheel[pInfo->SlotEntry]), &pInfo->QueueEntry ); UlReleaseSpinLock( &g_TimerWheelMutex, OldIrql ); } // UlTimeoutInsertTimerWheel /***************************************************************************++ Routine Description: Arguments: --***************************************************************************/ VOID UlTimeoutRemoveTimerWheelEntry( PUL_TIMEOUT_INFO_ENTRY pInfo ) { KIRQL OldIrql; ASSERT( NULL != pInfo ); ASSERT( !IsListEmpty(&pInfo->QueueEntry) ); UlTrace(TIMEOUTS, ( "http!UlTimeoutRemoveTimerWheelEntry: pInfo %p\n", pInfo )); UlAcquireSpinLock( &g_TimerWheelMutex, &OldIrql ); if (pInfo->QueueEntry.Flink != NULL) { RemoveEntryList( &pInfo->QueueEntry ); pInfo->QueueEntry.Flink = NULL; } UlReleaseSpinLock( &g_TimerWheelMutex, OldIrql ); } // UlTimeoutRemoveTimerWheelEntry /***************************************************************************++ Routine Description: Set the per Site Connection Timeout Value override Arguments: pInfo Timeout info block TimeoutValue Override value --***************************************************************************/ VOID UlSetPerSiteConnectionTimeoutValue( PUL_TIMEOUT_INFO_ENTRY pInfo, LONGLONG TimeoutValue ) { ASSERT( NULL != pInfo ); ASSERT( 0L != TimeoutValue ); PAGED_CODE(); UlTrace(TIMEOUTS, ( "http!UlSetPerSiteConnectionTimeoutValue: pInfo %p TimeoutValue %I64X.\n", pInfo, TimeoutValue )); ExInterlockedCompareExchange64( &pInfo->ConnectionTimeoutValue, // Destination &TimeoutValue, // Exchange &pInfo->ConnectionTimeoutValue, // Comperand &pInfo->Lock.KSpinLock // Lock ); } // UlSetPerSiteConnectionTimeoutValue /***************************************************************************++ Routine Description: Starts a given timer in the timer info block. Arguments: pInfo Timer info block Timer Timer to set --***************************************************************************/ VOID UlSetConnectionTimer( PUL_TIMEOUT_INFO_ENTRY pInfo, CONNECTION_TIMEOUT_TIMER Timer ) { LARGE_INTEGER Now; ASSERT( NULL != pInfo ); ASSERT( IS_VALID_TIMEOUT_TIMER(Timer) ); ASSERT( UlDbgSpinLockOwned( &pInfo->Lock ) ); UlTrace(TIMEOUTS, ( "http!UlSetConnectionTimer: pInfo %p Timer %s\n", pInfo, g_aTimeoutTimerNames[Timer] )); // // Get current time // KeQuerySystemTime(&Now); // // Set timer to apropriate value // switch ( Timer ) { case TimerConnectionIdle: case TimerEntityBody: case TimerResponse: // all can be handled with the same timeout value pInfo->Timers[Timer] = TIMER_WHEEL_TICKS(Now.QuadPart + pInfo->ConnectionTimeoutValue); break; case TimerHeaderWait: pInfo->Timers[TimerHeaderWait] = TIMER_WHEEL_TICKS(Now.QuadPart + g_TM_HeaderWaitTimeout); break; // NOTE: TimerMinKBSec is handled in UlSetMinKBSecTimer() default: UlTrace(TIMEOUTS, ( "http!UlSetConnectionTimer: Bad Timer! (%d)\n", Timer )); ASSERT( FALSE ); } } // UlSetConnectionTimer /***************************************************************************++ Routine Description: Turns on the MinKBSec timer, adds the number of secs given the minimum bandwidth specified. Arguments: pInfo Timer info block BytesToSend Bytes to be sent --***************************************************************************/ VOID UlSetMinKBSecTimer( PUL_TIMEOUT_INFO_ENTRY pInfo, LONGLONG BytesToSend ) { LONGLONG XmitTicks; KIRQL OldIrql; ULONG NewTick; BOOLEAN bCallEvaluate = FALSE; ASSERT( NULL != pInfo ); UlTrace(TIMEOUTS, ( "http!UlSetMinKBSecTimer: pInfo %p BytesToSend %ld\n", pInfo, BytesToSend )); if ( g_TM_MinKBSecDivisor ) { if ( 0L != BytesToSend ) { // // Calculate the estimated time required to send BytesToSend // XmitTicks = BytesToSend / g_TM_MinKBSecDivisor; if (0 == XmitTicks) { XmitTicks = C_NS_TICKS_PER_SEC; } else { XmitTicks *= C_NS_TICKS_PER_SEC; } UlAcquireSpinLock( &pInfo->Lock, &OldIrql ); if ( TIMER_OFF_SYSTIME == pInfo->MinKBSecSystemTime ) { LARGE_INTEGER Now; // // Get current time // KeQuerySystemTime(&Now); pInfo->MinKBSecSystemTime = (Now.QuadPart + XmitTicks); } else { pInfo->MinKBSecSystemTime += XmitTicks; } NewTick = TIMER_WHEEL_TICKS(pInfo->MinKBSecSystemTime); if ( NewTick != pInfo->Timers[TimerMinKBSec] ) { bCallEvaluate = TRUE; pInfo->Timers[TimerMinKBSec] = NewTick; } UlReleaseSpinLock( &pInfo->Lock, OldIrql ); if ( TRUE == bCallEvaluate ) { UlEvaluateTimerState(pInfo); } } } } // UlSetMinKBSecTimer /***************************************************************************++ Routine Description: Turns off a given timer in the timer info block. Arguments: pInfo Timer info block Timer Timer to reset --***************************************************************************/ VOID UlResetConnectionTimer( PUL_TIMEOUT_INFO_ENTRY pInfo, CONNECTION_TIMEOUT_TIMER Timer ) { ASSERT( NULL != pInfo ); ASSERT( IS_VALID_TIMEOUT_TIMER(Timer) ); ASSERT( UlDbgSpinLockOwned( &pInfo->Lock ) ); UlTrace(TIMEOUTS, ( "http!UlResetConnectionTimer: pInfo %p Timer %s\n", pInfo, g_aTimeoutTimerNames[Timer] )); // // Turn off timer // pInfo->Timers[Timer] = TIMER_OFF_TICK; if (TimerMinKBSec == Timer) { pInfo->MinKBSecSystemTime = TIMER_OFF_SYSTIME; } } // UlResetConnectionTimer /***************************************************************************++ Routine Description: Turns off all timers Arguments: pInfo Timer info block --***************************************************************************/ VOID UlResetAllConnectionTimers( PUL_TIMEOUT_INFO_ENTRY pInfo ) { int i; ASSERT( NULL != pInfo ); ASSERT( UlDbgSpinLockOwned( &pInfo->Lock ) ); for ( i = 0; i < TimerMaximumTimer; i++ ) { pInfo->Timers[i] = TIMER_OFF_TICK; } pInfo->CurrentTimer = TimerConnectionIdle; pInfo->CurrentExpiry = TIMER_OFF_TICK; pInfo->MinKBSecSystemTime = TIMER_OFF_SYSTIME; } // // Private functions // /***************************************************************************++ Routine Description: Arguments: --***************************************************************************/ VOID UlEvaluateTimerState( PUL_TIMEOUT_INFO_ENTRY pInfo ) { int i; ULONG MinTimeout = TIMER_OFF_TICK; CONNECTION_TIMEOUT_TIMER MinTimeoutTimer = TimerConnectionIdle; ASSERT( NULL != pInfo ); ASSERT( !UlDbgSpinLockOwned( &pInfo->Lock ) ); for ( i = 0; i < TimerMaximumTimer; i++ ) { if (pInfo->Timers[i] < MinTimeout) { MinTimeout = pInfo->Timers[i]; MinTimeoutTimer = (CONNECTION_TIMEOUT_TIMER) i; } } // // If we've found a different expiry time, update expiry state. // if (pInfo->CurrentExpiry != MinTimeout) { KIRQL OldIrql; #if DBG LARGE_INTEGER Now; KeQuerySystemTime(&Now); ASSERT( MinTimeout >= TIMER_WHEEL_TICKS(Now.QuadPart) ); #endif // DBG // // Calculate new slot // InterlockedExchange( (LONG *) &pInfo->SlotEntry, UlpTimerWheelTicksToTimerWheelSlot(MinTimeout) ); // // Move to new slot if necessary // if ( (pInfo->SlotEntry != TIMER_OFF_SLOT) && (MinTimeout < pInfo->CurrentExpiry) ) { // // Only move if it's on the Wheel; If Flink is null, it's in // the process of being expired. // if ( NULL != pInfo->QueueEntry.Flink ) { UlAcquireSpinLock( &g_TimerWheelMutex, &OldIrql ); UlTrace(TIMEOUTS, ( "http!UlEvaluateTimerInfo: pInfo %p: Moving to new slot %d\n", pInfo, pInfo->SlotEntry )); RemoveEntryList( &pInfo->QueueEntry ); InsertTailList( &(g_TimerWheel[pInfo->SlotEntry]), &pInfo->QueueEntry ); UlReleaseSpinLock( &g_TimerWheelMutex, OldIrql ); } } // // Update timer wheel state // pInfo->CurrentExpiry = MinTimeout; pInfo->CurrentTimer = MinTimeoutTimer; } } // UlpEvaluateTimerState