|
|
/*++
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_MaxValueForVirtualMemoryBasedInKB = 0; SIZE_T WP_RECYCLER::sm_MaxValueForPrivateBytesBasedInKB = 0; DWORD WP_RECYCLER::sm_CurrentPID = NULL; BUFFER WP_RECYCLER::sm_buffSystemProcessInfo = NULL; //
// arbitrary value for default SystemProcessInfoBuffer
// it will be resized when needed
//
DWORD WP_RECYCLER::sm_cbSystemProcessInfo = sizeof(SYSTEM_PROCESS_INFORMATION) * 50;
//
// 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
--*/
{ 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, BOOLEAN )
/*++
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, IN DWORD dwMaxPrivateBytesUsageInKB ) /*++
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(VM:%d kB, Private Bytes:%d kB)\n", dwMaxVirtualMemoryUsageInKB, dwMaxPrivateBytesUsageInKB)); }
//
// 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 && dwMaxPrivateBytesUsageInKB == 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_CurrentPID = GetCurrentProcessId(); sm_MaxValueForVirtualMemoryBasedInKB = dwMaxVirtualMemoryUsageInKB; sm_MaxValueForPrivateBytesBasedInKB = dwMaxPrivateBytesUsageInKB; 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 --*/
{ 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; }
sm_fIsStartedMemoryBased = FALSE; LeaveCriticalSection( &WP_RECYCLER::sm_CritSec ); return;
}
//static
VOID WINAPI WP_RECYCLER::TimerCallbackForMemoryBased( PVOID, BOOLEAN ) /*++
Routine Description:
Timer callback for Elapsed time based recycling This Callback is passed to CreateTimerQueueTimer()
Virtual memory and Private Bytes 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; PSYSTEM_PROCESS_INFORMATION pProcessInfo = NULL;
DBG_ASSERT( WP_RECYCLER::sm_fIsStartedMemoryBased );
//
// Keep trying larger buffers until we get all the information
// Note: There seems to be no easier way to get PrivateBytes counter
// for the process other then to enumerate all the processes
// This may cause performance problem with large number of worker processes
// that are self monitoring on memory usage
//
for(;;) { if ( !sm_buffSystemProcessInfo.Resize( sm_cbSystemProcessInfo ) ) { // We failed to reallocate then just silently exit
// CODEWORK: This may cause worker process to never recycle
// if memory is too low to handle memory usage check
//
return; } Status = NtQuerySystemInformation( SystemProcessInformation, sm_buffSystemProcessInfo.QueryPtr(), sm_buffSystemProcessInfo.QuerySize(), NULL );
if( Status != STATUS_INFO_LENGTH_MISMATCH ) break;
sm_cbSystemProcessInfo *= 2; }
if( Status == STATUS_SUCCESS ) { DWORD NextOffset = 0; //
// enumerate info about all processes until you find
// the current process (identified by PID)
//
for(;;) { //
// get process info from buffer
//
pProcessInfo = (PSYSTEM_PROCESS_INFORMATION) (((PBYTE) sm_buffSystemProcessInfo.QueryPtr()) + NextOffset ); NextOffset += pProcessInfo->NextEntryOffset;
//
// Compare Pid to see if we found the current process
//
if ( HandleToULong( pProcessInfo->UniqueProcessId ) == sm_CurrentPID ) { break; }
if ( pProcessInfo->NextEntryOffset == 0 ) { //
// Well, we should never get here because the current process
// must be listed but to eliminate potential problems
// since we are using private NT API let's assume not found error
//
Status = STATUS_NOT_FOUND; break; } } }
if ( ! NT_SUCCESS ( Status ) ) { IF_DEBUG( WPRECYCLER ) { DBGPRINTF(( DBG_CONTEXT, "NtQueryInformationProcess failed with Status: %d\n", Status )); } return; }
//
// Check virtual bytes
//
if ( sm_MaxValueForVirtualMemoryBasedInKB != 0 && pProcessInfo->VirtualSize/1024 >= sm_MaxValueForVirtualMemoryBasedInKB ) { IF_DEBUG( WPRECYCLER ) { DBGPRINTF(( DBG_CONTEXT, "WP_RECYCLER::TimerCallbackForMemoryBased()" " - current VM:%ld kB, configured max VM:%ld kB" " - tell WAS to recycle\n", pProcessInfo->VirtualSize/1024 , sm_MaxValueForVirtualMemoryBasedInKB )); }
//
// we reached Virtual Memory Usage limit
//
WP_RECYCLER::SendRecyclingMsg( IPM_WP_RESTART_VIRTUAL_MEMORY_LIMIT_REACHED ); }
//
// Check private bytes
//
if ( sm_MaxValueForPrivateBytesBasedInKB != 0 && pProcessInfo->PrivatePageCount/1024 >= sm_MaxValueForPrivateBytesBasedInKB ) { IF_DEBUG( WPRECYCLER ) { DBGPRINTF(( DBG_CONTEXT, "WP_RECYCLER::TimerCallbackForMemoryBased()" " - current Private Bytes:%ld kB, configured max Private Bytes:%ld kB" " - tell WAS to recycle\n", pProcessInfo->PrivatePageCount/1024 , sm_MaxValueForPrivateBytesBasedInKB )); }
//
// we reached Private Memory Usage limit
//
WP_RECYCLER::SendRecyclingMsg( IPM_WP_RESTART_PRIVATE_BYTES_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
--*/
{ 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, BOOLEAN ) /*++
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;
}
|