windows-nt/Source/XPSP1/NT/inetsrv/iis/iisrearc/iisplus/ulatq/wprecycler.cxx
2020-09-26 16:20:57 +08:00

1049 lines
23 KiB
C++

/*++
Copyright (c) 1998 Microsoft Corporation
Module Name :
wprecycler.cxx
Abstract:
Implementation of WP_RECYCLER. Object handles worker process recycling
- Memory based recycling
- Schedule based recycling
- Elapsed Time based recycling
- Processed Request Count based recycling
Dependencies:
g_pwpContext is used by WP_RECYCLER to be able to send messages
Author:
Jaroslav Dunajsky (JaroslaD) 07-Dec-2000
Environment:
Win32 - User Mode
Project:
W3DT.DLL
--*/
#include "precomp.hxx"
#include "wprecycler.hxx"
#define ONE_DAY_IN_MILLISECONDS (1000 * 60 * 60 * 24)
//
// Static variables
//
CRITICAL_SECTION WP_RECYCLER::sm_CritSec;
//
// Static variables for Memory based recycling
//
HANDLE WP_RECYCLER::sm_hTimerForMemoryBased = NULL;
BOOL WP_RECYCLER::sm_fIsStartedMemoryBased = FALSE;
SIZE_T WP_RECYCLER::sm_MaxValueForMemoryBased = 0;
HANDLE WP_RECYCLER::sm_hCurrentProcess = NULL;
//
// Static variables for Time based recycling
//
HANDLE WP_RECYCLER::sm_hTimerForTimeBased = NULL;
BOOL WP_RECYCLER::sm_fIsStartedTimeBased = FALSE;
//
// Static variables for Schedule based recycling
//
HANDLE WP_RECYCLER::sm_hTimerQueueForScheduleBased = NULL;
BOOL WP_RECYCLER::sm_fIsStartedScheduleBased = FALSE;
//
// Static variables for Request based recycling
//
BOOL WP_RECYCLER::sm_fIsStartedRequestBased = FALSE;
DWORD WP_RECYCLER::sm_dwMaxValueForRequestBased = 0;
LONG WP_RECYCLER::sm_RecyclingMsgSent = 0;
BOOL WP_RECYCLER::sm_fCritSecInit = FALSE;
//
// Static methods for Schedule based recycling
//
//static
HRESULT
WP_RECYCLER::StartScheduleBased(
IN const WCHAR * pwszScheduleTimes
)
/*++
Routine Description:
Start schedule based recycling
Arguments:
pwszScheduleTimes - MULTISZ array of time information
<time>\0<time>\0\0
time is of military format hh:mm
(hh>=0 && hh<=23)
(mm>=0 && hh<=59)
Return Value:
HRESULT
--*/
{
HRESULT hr = E_FAIL;
BOOL fRet = FALSE;
const WCHAR * pwszCurrentChar = pwszScheduleTimes;
HANDLE hTimer;
WORD wHours = 0;
WORD wMinutes = 0;
WORD wDigitCount = 0;
SYSTEMTIME SystemTime;
FILETIME FileTime;
FILETIME CurrentFileTime;
ULARGE_INTEGER largeintCurrentTime;
ULARGE_INTEGER largeintTime;
DWORD dwDueTime = 0;
DBG_ASSERT(TRUE == sm_fCritSecInit);
EnterCriticalSection( &WP_RECYCLER::sm_CritSec );
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"WP_RECYCLER::StartScheduleBased()\n"));
}
DBG_ASSERT( pwszScheduleTimes != NULL );
//
// If scheduler based recycling has been running already
// terminate it before restarting with new settings
//
if ( WP_RECYCLER::sm_fIsStartedScheduleBased )
{
WP_RECYCLER::TerminateScheduleBased();
}
WP_RECYCLER::sm_hTimerQueueForScheduleBased = CreateTimerQueue();
if ( WP_RECYCLER::sm_hTimerQueueForScheduleBased == NULL )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
goto Failed;
}
//
// Gets current time
//
GetLocalTime( &SystemTime );
SystemTimeToFileTime( &SystemTime,
&CurrentFileTime );
memcpy( &largeintCurrentTime,
&CurrentFileTime,
sizeof( ULARGE_INTEGER ) );
//
// empty string in MULTISZ indicates the end of MULTISZ
//
while ( *pwszCurrentChar != '\0' )
{
//
// Skip white spaces
//
while ( iswspace( (wint_t) *pwszCurrentChar ) )
{
pwszCurrentChar++;
}
//
// Start of the time info
// Expect military format hh:mm
//
//
// Process hours (up to 2 digits is valid)
//
wHours = 0;
wDigitCount = 0;
while ( iswdigit( *pwszCurrentChar ) )
{
wDigitCount++;
wHours = 10 * wHours + (*pwszCurrentChar - '0');
pwszCurrentChar++;
}
if ( wDigitCount > 2 ||
( wHours > 23 ) )
{
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
goto Failed;
}
//
// Hours - minutes separator
// Be liberal - any character that is not a digit or '\0' is OK
//
if ( *pwszCurrentChar == '\0' )
{
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
goto Failed;
}
pwszCurrentChar++;
//
// Process minutes (must be exactly 2 digits)
//
wMinutes = 0;
wDigitCount = 0;
while ( iswdigit( (wint_t) *pwszCurrentChar ) )
{
wDigitCount++;
wMinutes = 10 * wMinutes + (*pwszCurrentChar - '0');
pwszCurrentChar++;
}
if ( ( wDigitCount != 2 ) ||
( wMinutes > 59 ) )
{
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
goto Failed;
}
//
// Skip white spaces
//
while ( iswspace( (wint_t)*pwszCurrentChar ) )
{
pwszCurrentChar++;
}
//
// Check for terminating zero
//
if ( *pwszCurrentChar != '\0' )
{
//
// Extra characters in the time string
//
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
goto Failed;
}
pwszCurrentChar++;
//
// Convert Hours and Minutes info
//
SystemTime.wHour = wHours;
SystemTime.wMinute = wMinutes;
SystemTime.wSecond = 0;
SystemTime.wMilliseconds = 0;
SystemTimeToFileTime( &SystemTime,
&FileTime );
memcpy( &largeintTime,
&FileTime,
sizeof(ULARGE_INTEGER) );
//
// Issue 12/21/2000 jaroslad:
// This method of setting absolute time with CreateTimerQueueTimer
// is bad since instead of setting absolute time the relative time is
// calculated and used for timer.
// This approach fails badly if someone changes machine
// time. Other Api that enables setting abolute time must be used for proper
// implementation
//
// Get Due Time in milliseconds
//
dwDueTime = static_cast<DWORD>(
( largeintTime.QuadPart - largeintCurrentTime.QuadPart )/ 10000);
if ( largeintTime.QuadPart < largeintCurrentTime.QuadPart)
{
dwDueTime = ONE_DAY_IN_MILLISECONDS - static_cast<DWORD>(
( largeintCurrentTime.QuadPart - largeintTime.QuadPart )/ 10000);
}
else
{
dwDueTime = static_cast<DWORD>(
( largeintTime.QuadPart - largeintCurrentTime.QuadPart )/ 10000);
}
if ( dwDueTime == 0 )
{
//
// this event is to be scheduled for the next day
// one day has 1000 * 60 * 60 * 24 of (100-nanosecond intervals)
//
dwDueTime += ONE_DAY_IN_MILLISECONDS;
}
//
// Schedule event for specified time, repeating once a day
//
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"Schedule recycling for %d:%d (in %d milliseconds)\n",
(int) wHours,
(int) wMinutes,
dwDueTime));
}
fRet = CreateTimerQueueTimer(
&hTimer,
WP_RECYCLER::sm_hTimerQueueForScheduleBased,
WP_RECYCLER::TimerCallbackForScheduleBased,
NULL,
dwDueTime,
// repeat daily (interval in milliseconds)
ONE_DAY_IN_MILLISECONDS,
WT_EXECUTELONGFUNCTION );
if ( !fRet )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
goto Failed;
}
//
// hTimer will not be stored
// sm_hTimerQueueForScheduleBased is going to be used for cleanup
// DeleteTimerQueueEx() should be able to correctly delete all timers
// in the queue
//
}
WP_RECYCLER::sm_fIsStartedScheduleBased = TRUE;
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return S_OK;
Failed:
WP_RECYCLER::TerminateScheduleBased();
DBG_ASSERT( FAILED( hr ) );
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"WP_RECYCLER::StartScheduleBased() failed with error hr=0x%x\n",
hr ));
}
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return hr;
}
//static
VOID
WP_RECYCLER::TerminateScheduleBased(
VOID
)
/*++
Routine Description:
Stops schedule based recycling
Performs cleanup
Note:
It is safe to call this method for cleanup if Start failed
Arguments:
NONE
Return Value:
VOID
--*/
{
HRESULT hr = E_FAIL;
DBG_ASSERT(TRUE == sm_fCritSecInit);
EnterCriticalSection( &WP_RECYCLER::sm_CritSec );
if( WP_RECYCLER::sm_hTimerQueueForScheduleBased != NULL )
{
if ( !DeleteTimerQueueEx(
WP_RECYCLER::sm_hTimerQueueForScheduleBased,
INVALID_HANDLE_VALUE /* wait for callbacks to complete */
) )
{
DBGPRINTF(( DBG_CONTEXT,
"failed to call DeleteTimerQueueEx(): hr=0x%x\n",
HRESULT_FROM_WIN32(GetLastError()) ));
}
WP_RECYCLER::sm_hTimerQueueForScheduleBased = NULL;
}
WP_RECYCLER::sm_fIsStartedScheduleBased = FALSE;
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return;
}
//static
VOID
WINAPI
WP_RECYCLER::TimerCallbackForScheduleBased(
PVOID pParam,
BOOLEAN TimerOrWaitFired
)
/*++
Routine Description:
Timer callback for Schedule based recycling
It is passed to CreateTimerQueueTimer()
Routine will inform WAS that process is ready to be recycled
because scheduled time has been reached
Arguments:
see the description of WAITORTIMERCALLBACK type in MSDN
Return Value:
none
--*/
{
DBG_ASSERT( WP_RECYCLER::sm_fIsStartedScheduleBased );
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"WP_RECYCLER::TimerCallbackForScheduleBased()"
" - tell WAS to recycle\n" ));
}
//
// Indicate to WAS that we are ready for recycling
//
WP_RECYCLER::SendRecyclingMsg( IPM_WP_RESTART_SCHEDULED_TIME_REACHED );
}
//
// Static methods for Memory based recycling
//
//static
HRESULT
WP_RECYCLER::StartMemoryBased(
IN DWORD dwMaxVirtualMemoryUsageInKB
)
/*++
Routine Description:
Start virtual memory usage based recycling.
Arguments:
dwMaxVirtualMemoryUsageInKB - If usage of virtual memory reaches this value
worker process is ready for recycling
Note:
VM usage will be checked periodically. See the value of internal constant
CHECK_MEMORY_TIME_PERIOD
Return Value:
HRESULT
--*/
{
HRESULT hr = E_FAIL;
BOOL fRet = FALSE;
DBG_ASSERT(TRUE == sm_fCritSecInit);
EnterCriticalSection( &WP_RECYCLER::sm_CritSec );
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"WP_RECYCLER::StartMemoryBased(%d kB)\n",
dwMaxVirtualMemoryUsageInKB ));
}
//
// If time based recycling has been running already
// terminate it before restarting with new settings
//
if ( WP_RECYCLER::sm_fIsStartedMemoryBased == TRUE )
{
WP_RECYCLER::TerminateMemoryBased();
}
if ( dwMaxVirtualMemoryUsageInKB == 0 )
{
//
// 0 means not to run memory recycling
//
hr = S_OK;
goto succeeded;
}
fRet = CreateTimerQueueTimer( &WP_RECYCLER::sm_hTimerForMemoryBased,
NULL,
WP_RECYCLER::TimerCallbackForMemoryBased,
NULL,
CHECK_MEMORY_TIME_PERIOD,
CHECK_MEMORY_TIME_PERIOD,
WT_EXECUTELONGFUNCTION );
if ( !fRet )
{
WP_RECYCLER::sm_hTimerForMemoryBased = NULL;
hr = HRESULT_FROM_WIN32( GetLastError() );
goto failed;
}
//
// Get current process handle
// It will be used for NtQueryInformationProcess()
// in the timer callback
// there is no error to check for and handle doesn't need to be closed
// on cleanup
//
sm_hCurrentProcess = GetCurrentProcess();
sm_MaxValueForMemoryBased = 1024 * dwMaxVirtualMemoryUsageInKB;
WP_RECYCLER::sm_fIsStartedMemoryBased = TRUE;
succeeded:
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return S_OK;
failed:
DBG_ASSERT( FAILED( hr ) );
WP_RECYCLER::TerminateMemoryBased();
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"WP_RECYCLER::StartMemoryBased() failed with error hr=0x%x\n",
hr ));
}
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return hr;
}
//static
VOID
WP_RECYCLER::TerminateMemoryBased(
VOID
)
/*++
Routine Description:
Stops virtual memory usage based recycling
Performs cleanup
Note:
It is safe to call this method for cleanup if Start failed
Arguments:
NONE
Return Value:
VOID
--*/
{
HRESULT hr = E_FAIL;
DBG_ASSERT(TRUE == sm_fCritSecInit);
EnterCriticalSection( &WP_RECYCLER::sm_CritSec );
if ( WP_RECYCLER::sm_hTimerForMemoryBased != NULL )
{
if ( !DeleteTimerQueueTimer(
NULL,
WP_RECYCLER::sm_hTimerForMemoryBased,
INVALID_HANDLE_VALUE /* wait for callbacks to complete */
) )
{
DBGPRINTF(( DBG_CONTEXT,
"failed to call DeleteTimerQueueTimer(): hr=0x%x\n",
HRESULT_FROM_WIN32(GetLastError()) ));
}
WP_RECYCLER::sm_hTimerForMemoryBased = NULL;
}
WP_RECYCLER::sm_fIsStartedMemoryBased = FALSE;
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return;
}
//static
VOID
WINAPI
WP_RECYCLER::TimerCallbackForMemoryBased(
PVOID pParam,
BOOLEAN TimerOrWaitFired
)
/*++
Routine Description:
Timer callback for Elapsed time based recycling
This Callback is passed to CreateTimerQueueTimer()
Virtual memory usage will be checked and if limit has been reached
then routine will inform WAS that process is ready to be recycled
Arguments:
see description of WAITORTIMERCALLBACK type in MSDN
Return Value:
none
--*/
{
NTSTATUS Status = 0;
VM_COUNTERS VmCounters;
DBG_ASSERT( WP_RECYCLER::sm_fIsStartedMemoryBased );
Status = NtQueryInformationProcess( sm_hCurrentProcess,
ProcessVmCounters,
&VmCounters,
sizeof(VM_COUNTERS),
NULL );
if ( ! NT_SUCCESS ( Status ) )
{
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"NtQueryInformationProcess failed with Status: %d\n",
Status ));
}
return;
}
if ( VmCounters.VirtualSize >= sm_MaxValueForMemoryBased )
{
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"WP_RECYCLER::TimerCallbackForMemoryBased()"
" - current VM:%ld kB, configured max VM:%ld kB"
" - tell WAS to recycle\n",
VmCounters.VirtualSize/1024 ,
sm_MaxValueForMemoryBased/1024 ));
}
//
// we reached Virtual Memory Usage limit
//
WP_RECYCLER::SendRecyclingMsg( IPM_WP_RESTART_MEMORY_LIMIT_REACHED );
}
}
//
// Static methods for Time based recycling
//
//static
HRESULT
WP_RECYCLER::StartTimeBased(
IN DWORD dwPeriodicRestartTimeInMinutes
)
/*++
Routine Description:
Start elapsed time based recycling
Arguments:
dwPeriodicRestartTimeInMinutes - how often to restart (in minutes)
Return Value:
HRESULT
--*/
{
HRESULT hr = E_FAIL;
BOOL fRet = FALSE;
DBG_ASSERT(TRUE == sm_fCritSecInit);
EnterCriticalSection( &WP_RECYCLER::sm_CritSec );
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"WP_RECYCLER::StartTimeBased(%d min)\n" ,
dwPeriodicRestartTimeInMinutes ));
}
//
// If time based recycling has been running already
// terminate it before restarting with new settings
//
if ( WP_RECYCLER::sm_fIsStartedTimeBased == TRUE )
{
WP_RECYCLER::TerminateTimeBased();
}
if ( dwPeriodicRestartTimeInMinutes == 0 )
{
//
// 0 means not to run time based recycling
//
hr = S_OK;
goto succeeded;
}
fRet = CreateTimerQueueTimer( &WP_RECYCLER::sm_hTimerForTimeBased,
NULL,
WP_RECYCLER::TimerCallbackForTimeBased,
NULL,
dwPeriodicRestartTimeInMinutes * 60000,
// convert to msec
dwPeriodicRestartTimeInMinutes * 60000,
// convert to msec
WT_EXECUTELONGFUNCTION );
if ( !fRet )
{
WP_RECYCLER::sm_hTimerForTimeBased = NULL;
hr = HRESULT_FROM_WIN32( GetLastError() );
goto failed;
}
WP_RECYCLER::sm_fIsStartedTimeBased = TRUE;
succeeded:
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return S_OK;
failed:
DBG_ASSERT( FAILED( hr ) );
WP_RECYCLER::TerminateTimeBased();
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"WP_RECYCLER::StartTimeBased() failed with error hr=0x%x\n",
hr));
}
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return hr;
}
//static
VOID
WP_RECYCLER::TerminateTimeBased(
VOID
)
/*++
Routine Description:
Stops elapsed time based recycling
Performs cleanup
Note:
It is safe to call this method for cleanup if Start failed
Arguments:
NONE
Return Value:
HRESULT
--*/
{
HRESULT hr = E_FAIL;
DBG_ASSERT(TRUE == sm_fCritSecInit);
EnterCriticalSection( &WP_RECYCLER::sm_CritSec );
if ( WP_RECYCLER::sm_hTimerForTimeBased != NULL )
{
if ( !DeleteTimerQueueTimer(
NULL,
WP_RECYCLER::sm_hTimerForTimeBased,
INVALID_HANDLE_VALUE /* wait for callbacks to complete */
) )
{
DBGPRINTF(( DBG_CONTEXT,
"failed to call DeleteTimerQueueTimer(): hr=0x%x\n",
HRESULT_FROM_WIN32(GetLastError()) ));
}
WP_RECYCLER::sm_hTimerForTimeBased = NULL;
}
WP_RECYCLER::sm_fIsStartedTimeBased = FALSE;
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return;
}
//static
VOID
WINAPI
WP_RECYCLER::TimerCallbackForTimeBased(
PVOID pParam,
BOOLEAN TimerOrWaitFired
)
/*++
Routine Description:
Timer callback for Elapsed time based recycling
This Callback is passed to CreateTimerQueueTimer()
Routine will inform WAS that process is ready to be recycled
because required elapsed time has been reached
Arguments:
see description of WAITORTIMERCALLBACK type in MSDN
Return Value:
none
--*/
{
DBG_ASSERT( WP_RECYCLER::sm_fIsStartedTimeBased );
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"WP_RECYCLER::TimerCallbackForTimeBased"
" - tell WAS to recycle\n" ));
}
WP_RECYCLER::SendRecyclingMsg( IPM_WP_RESTART_ELAPSED_TIME_REACHED );
}
//
// Static methods for Request based recycling
//
//static
HRESULT
WP_RECYCLER::StartRequestBased(
IN DWORD dwRequests
)
/*++
Routine Description:
Start request based recycling.
Arguments:
dwRequests - If number of requests processed by worker process reaches this value
recycling will be required
Return Value:
HRESULT
--*/
{
HRESULT hr = E_FAIL;
DBG_ASSERT(TRUE == sm_fCritSecInit);
EnterCriticalSection( &WP_RECYCLER::sm_CritSec );
IF_DEBUG( WPRECYCLER )
{
DBGPRINTF(( DBG_CONTEXT,
"WP_RECYCLER::StartRequestBased(%d kB)\n" ,
dwRequests ));
}
//
// If time based recycling has been running already
// terminate it before restarting with new settings
//
if ( WP_RECYCLER::sm_fIsStartedRequestBased == TRUE )
{
WP_RECYCLER::TerminateRequestBased();
}
if ( dwRequests == 0 )
{
//
// 0 means not to run request based recycling
//
hr = S_OK;
goto succeeded;
}
InterlockedExchange(
reinterpret_cast<LONG *>(&sm_dwMaxValueForRequestBased),
dwRequests );
InterlockedExchange(
reinterpret_cast<LONG *>(&WP_RECYCLER::sm_fIsStartedTimeBased),
TRUE );
hr = S_OK;
succeeded:
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return hr;
}
//static
VOID
WP_RECYCLER::TerminateRequestBased(
VOID
)
/*++
Routine Description:
Stops request based recycling
Performs cleanup
Note:
It is safe to call this method for cleanup if Start failed
Arguments:
NONE
Return Value:
HRESULT
--*/
{
DBG_ASSERT(TRUE == sm_fCritSecInit);
EnterCriticalSection( &WP_RECYCLER::sm_CritSec );
//
// InterlockedExchange is used because Request Based recycling callback
// IsRequestCountLimitReached() is called for each request
// and we don't synchronize it with &WP_RECYCLER::sm_CritSec
//
InterlockedExchange(
reinterpret_cast<LONG *>(&WP_RECYCLER::sm_fIsStartedTimeBased),
FALSE );
LeaveCriticalSection( &WP_RECYCLER::sm_CritSec );
return;
}