mirror of https://github.com/tongzx/nt5src
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.
1293 lines
28 KiB
1293 lines
28 KiB
/*++
|
|
|
|
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
|
|
|
|
|