|
|
/*++
Copyright (c) 1998 Microsoft Corporation
Module Name : wpcontext.cxx
Abstract: This module defines the member functions of the WP_CONTEXT. The WP_CONTEXT object embodies an instance of the Worker process object. It contains a completion port, pool of worker threads, pool of worker requests, a data channel for the worker process, etc. It is responsible for setting up the context for processing requests and handles delegating the processing of requests.
NYI: In the future we should be able to run WP_CONTEXT object as a COM+ object and be run standalone using a hosting exe.
Author:
Murali R. Krishnan ( MuraliK ) 17-Nov-1998
Project:
IIS Worker Process
--*/
#include "precomp.hxx"
VOID WINAPI IdleTimeCheckCallback( VOID * pvContext, BOOLEAN ) /*++
Routine Description:
Callback function provided for TimerQueue. Called every minute
Arguments:
pvContext - Context
Return Value:
None
--*/ { WP_IDLE_TIMER * pTimer = (WP_IDLE_TIMER *)pvContext;
DBGPRINTF(( DBG_CONTEXT, "Check Idle Time Callback.\n" ));
DBG_ASSERT( pTimer );
pTimer->IncrementTick(); }
WP_IDLE_TIMER::WP_IDLE_TIMER( ULONG IdleTime ) : _BusySignal(0), _CurrentIdleTick(0), _IdleTime(IdleTime), _hIdleTimeExpiredTimer((HANDLE)NULL) { }
WP_IDLE_TIMER::~WP_IDLE_TIMER( VOID ) { //
// Cancel IdleTimeExpiredTimer
//
if (_hIdleTimeExpiredTimer) { StopTimer(); } }
HRESULT WP_IDLE_TIMER::Initialize( VOID ) /*++
Routine Description:
Initialize the idle timer. Setup NT thread pool to callback every minute
Arguments:
None
Return Value:
HRESULT
--*/ { BOOL fRet; HRESULT hr = NO_ERROR;
//
// IdleTime is stored as in minutes, 1 min = 60*1000 milliseconds.
//
fRet = CreateTimerQueueTimer( &_hIdleTimeExpiredTimer, // handle to the Timer
NULL, // Default Timer Queue
IdleTimeCheckCallback, // Callback function
this, // Context.
60000, // Due Time
60000, // Signal every minute
WT_EXECUTEINIOTHREAD );
if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() );
DBGPRINTF(( DBG_CONTEXT, "Failed to create idle timer. hr = %x\n", hr )); }
return hr; }
VOID WP_IDLE_TIMER::IncrementTick( VOID ) /*++
Routine Description:
Check every minute whether we've been idle long enough. If so, tell WAS
Arguments:
None
Return Value:
None
--*/ { ULONG BusySignal = InterlockedExchange( (PLONG)&_BusySignal, 0 );
if ( !BusySignal && !UL_NATIVE_REQUEST::QueryCurrentRequests() ) { InterlockedIncrement( (PLONG)&_CurrentIdleTick );
if ( _CurrentIdleTick >= _IdleTime ) { DBGPRINTF(( DBG_CONTEXT, "Idle time reached. Send shutdown message to WAS.\n" ));
g_pwpContext->SendMsgToAdminProcess( IPM_WP_IDLE_TIME_REACHED ); } } else { _CurrentIdleTick = 0; } }
VOID WP_IDLE_TIMER::StopTimer( VOID ) /*++
Routine Description:
Remove timer
Arguments:
None
Return Value:
None
--*/ { BOOL fRet;
DBG_ASSERT( _hIdleTimeExpiredTimer );
fRet = DeleteTimerQueueTimer( NULL, _hIdleTimeExpiredTimer, (HANDLE)-1 );
if ( !fRet ) { DBGPRINTF(( DBG_CONTEXT, "Failed to delete Timer queue. Win32 = %ld\n", GetLastError() )); }
_hIdleTimeExpiredTimer = NULL; }
VOID OverlappedCompletionRoutine( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped ) /*++
Routine Description:
Main completion routine called on completions for UL app pool handle.
Arguments:
dwErrorCode - Win32 Error code of completion dwNumberOfBytesTransfered - Bytes completed lpOverlapped - Overlapped structure passed on async operation
Return Value:
None
--*/ { ASYNC_CONTEXT * pContext = NULL;
//
// Use the overlapped to get at the async context
//
if ( lpOverlapped != NULL ) { pContext = CONTAINING_RECORD( lpOverlapped, ASYNC_CONTEXT, _Overlapped ); DBG_ASSERT( pContext != NULL );
//
// Call virtual DoWork() to actually handle the completion
// (context can represent a UL_NATIVE_REQUEST or a UL_DISCONNECT)
//
pContext->DoWork( dwNumberOfBytesTransfered, dwErrorCode, lpOverlapped ); } else { DBG_ASSERT( lpOverlapped != NULL ); } }
WP_CONTEXT::WP_CONTEXT( VOID ) : _hDoneEvent( NULL ), _pConfigInfo( NULL ), _fShutdown( FALSE ), _pIdleTimer( NULL ), _fHealthy( TRUE ) { }
WP_CONTEXT::~WP_CONTEXT( VOID ) { }
HRESULT WP_CONTEXT::Initialize( INT argc, LPWSTR * argv ) /*++
Routine Description:
Initialize global context
Arguments:
argc - Command argument count argv - Command arguments
Return Value:
HRESULT
--*/ { LPCWSTR pwszAppPoolName; HRESULT hr = NO_ERROR; DWORD dwErr = NO_ERROR; BOOL fAppPoolInit = FALSE; BOOL fNativeRequestInit = FALSE; BOOL fDisconnectInit = FALSE; BOOL fIpmInit = FALSE; BOOL fWpRecyclerInit = FALSE;
_pConfigInfo = new WP_CONFIG(); if ( _pConfigInfo == NULL ) { hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); goto Finished; }
//
// Validate the parameters passed into executable
//
if ( !_pConfigInfo->ParseCommandLine( argc, argv ) ) { DBGPRINTF(( DBG_CONTEXT, "Invalid command line arguments.\n" ));
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto Finished; }
//
// If we need to establish the control channel to run
// without w3svc interaction do it now.
//
if ( _pConfigInfo->QuerySetupControlChannel() ) { dwErr = _pConfigInfo->SetupControlChannel(); if ( dwErr != ERROR_SUCCESS ) { hr = HRESULT_FROM_WIN32( dwErr );
DBGPRINTF(( DBG_CONTEXT, "SetupControlChannel failed with 0x%8x\n", hr ));
goto Finished; } }
pwszAppPoolName = _pConfigInfo->QueryAppPoolName();
//
// Initialize UL AppPool
//
hr = _ulAppPool.Initialize( pwszAppPoolName ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed to initialize AppPool. hr = %x\n", hr )); goto Finished; } SetEnvironmentVariableW( L"APP_POOL_ID", pwszAppPoolName ); fAppPoolInit = TRUE;
//
// Initialize UL_NATIVE_REQUEST globals
//
hr = UL_NATIVE_REQUEST::Initialize(); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed to initialize UL_NATIVE_REQUEST globals. hr = %x\n", hr )); goto Finished; } fNativeRequestInit = TRUE;
//
// Initialize UL_DISCONNECTs
//
hr = UL_DISCONNECT_CONTEXT::Initialize(); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed to initialize UL_DISCONNECT_CONTEXT globals. hr = %x\n", hr )); goto Finished; } fDisconnectInit = TRUE;
DBGPRINTF(( DBG_CONTEXT, "AppPool '%ws' initialized\n", pwszAppPoolName ));
//
// Initialize of the shutdown event
//
_hDoneEvent = IIS_CREATE_EVENT( "WP_CONTEXT::_hDoneEvent", &_hDoneEvent, TRUE, FALSE );
if ( _hDoneEvent == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() );
DBGPRINTF(( DBG_CONTEXT, "Failed to create shutdown event. hr = %x\n", hr )); goto Finished; }
//
// Setup all async completions on data channel handle to go thru W3TP
//
if (!ThreadPoolBindIoCompletionCallback( _ulAppPool.QueryAndLockHandle(), OverlappedCompletionRoutine, 0 )) { hr = HRESULT_FROM_WIN32(GetLastError()); DBGPRINTF(( DBG_CONTEXT, "Failed to associate handle with thread pool. hr = %x\n", hr )); _ulAppPool.UnlockHandle(); goto Finished; }
hr = _ulAppPool.UnlockHandle(); if ( FAILED( hr ) ) { goto Finished; }
//
// Need to init this first as we may start getting callbacks as soon
// as we init IPM
//
hr = WP_RECYCLER::Initialize();
if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed to initialize WP_RECYCLER. hr = %x\n", hr )); goto Finished; } fWpRecyclerInit = TRUE;
//
// Register with WAS
//
if ( _pConfigInfo->QueryRegisterWithWAS() ) { hr = _WpIpm.Initialize( this ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed to initialize IPM. hr = %x\n", hr )); goto Finished; } fIpmInit = TRUE; }
//
// Set the window title to something nice when we're running
// under the debugger.
//
if ( IsDebuggerPresent() ) { STRU strTitle; WCHAR buffer[sizeof("w3wp[1234567890] - ")]; WCHAR buffer2[sizeof(" - mm/dd hh:mm:ss")];
wsprintf( buffer, L"w3wp[%lu] - ", GetCurrentProcessId() ); hr = strTitle.Append( buffer );
if (SUCCEEDED(hr)) { hr = strTitle.Append( _pConfigInfo->QueryAppPoolName() ); }
if (SUCCEEDED(hr)) { LARGE_INTEGER sysTime; LARGE_INTEGER localTime; TIME_FIELDS fields;
NtQuerySystemTime( &sysTime ); RtlSystemTimeToLocalTime( &sysTime, &localTime ); RtlTimeToTimeFields( &localTime, &fields );
wsprintf( buffer2, L" - %02u/%02u %02u:%02u:%02u", fields.Month, fields.Day, fields.Hour, fields.Minute, fields.Second );
hr = strTitle.Append( buffer2 ); }
if (SUCCEEDED(hr)) { SetConsoleTitleW( strTitle.QueryStr() ); } }
return S_OK;
Finished:
//
// Terminate recycler object
// Dependency warning: _WpIpm must still be valid
//
if ( fWpRecyclerInit ) { WP_RECYCLER::Terminate(); }
if ( fIpmInit ) { _WpIpm.Terminate(); }
if ( _hDoneEvent != NULL ) { CloseHandle( _hDoneEvent ); _hDoneEvent = NULL; }
if ( fDisconnectInit ) { UL_DISCONNECT_CONTEXT::Terminate(); }
if ( fNativeRequestInit ) { UL_NATIVE_REQUEST::Terminate(); }
if ( fAppPoolInit ) { _ulAppPool.Cleanup(); }
if ( _pConfigInfo != NULL ) { delete _pConfigInfo; _pConfigInfo = NULL; }
return hr; }
HRESULT WP_CONTEXT::Start( VOID ) /*++
Routine Description:
Start listening for requests by creating UL_NATIVE_REQUESTs
Arguments:
None
Return Value:
HRESULT
--*/ { HRESULT hr = NO_ERROR;
//
// Create a pool of worker requests
// NYI: Allow the worker requests limit to be configurable.
//
UL_NATIVE_REQUEST::SetRestartCount( _pConfigInfo->QueryRestartCount() );
hr = UL_NATIVE_REQUEST::AddPendingRequests( (_pConfigInfo->QueryRestartCount() == 0 || (_pConfigInfo->QueryRestartCount() >= NUM_INITIAL_REQUEST_POOL_ITEMS)) ? NUM_INITIAL_REQUEST_POOL_ITEMS : _pConfigInfo->QueryRestartCount() ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed to add pending UL_NATIVE_REQUESTs. hr = %x\n", hr )); goto Finished; }
//
// There is a possibility that worker process runs out of pending requests
// in the case when http.sys starts failing HttpReceiveHttpRequest()
// We will avoid the problem by regularly checking the pending count
// adding new requests whenever necessary
//
hr = UL_NATIVE_REQUEST::StartPendingRequestsMonitor(); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed to start Pending requests monitor. hr = %x\n", hr )); goto Finished; }
//
// If an idle time is set, then set idle timer
//
if ( _pConfigInfo->QueryIdleTime() != 0 ) { _pIdleTimer = new WP_IDLE_TIMER( _pConfigInfo->QueryIdleTime() ); if ( _pIdleTimer ) { hr = _pIdleTimer->Initialize(); } else { hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); }
if ( FAILED( hr ) ) { goto Finished; } }
return S_OK; Finished:
if ( _pIdleTimer != NULL ) { delete _pIdleTimer; _pIdleTimer = NULL; }
return hr; }
BOOL WP_CONTEXT::IndicateShutdown( BOOL fImmediate ) /*++
Routine Description:
Set shutdown event which allows StartListen to wake up and begin cleanup
Arguments:
reason - Reason for shutdown
Return Value:
BOOL
--*/ { if ( !InterlockedCompareExchange((LONG *)&_fShutdown, TRUE, FALSE ) ) { _fImmediateShutdown = fImmediate;
return SetEvent( _hDoneEvent ); } else { return TRUE; } }
VOID WP_CONTEXT::Terminate( VOID ) /*++
Routine Description:
Cleanup WP_CONTEXT data structures
Arguments:
None
Return Value:
None
--*/ { HRESULT hr = NO_ERROR;
//
// Cleanup async contexts
//
UL_DISCONNECT_CONTEXT::Terminate();
UL_NATIVE_REQUEST::Terminate();
if ( _pIdleTimer != NULL ) { delete _pIdleTimer; _pIdleTimer = NULL; }
//
// Stop IPM
//
if ( _pConfigInfo->QueryRegisterWithWAS() ) { hr = _WpIpm.Terminate(); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed to shutdown IPM. hr = %x\n", hr )); } } //
// Cleanup the Shutdown Event.
//
DBG_ASSERT( _hDoneEvent != NULL );
CloseHandle( _hDoneEvent ); _hDoneEvent = NULL;
//
// Terminate procerr recycler.
// Dependency warning: _WpIpm must still be valid
//
WP_RECYCLER::Terminate();
//
// Cleanup config object
//
delete _pConfigInfo; _pConfigInfo = NULL; }
HRESULT WP_CONTEXT::CleanupOutstandingRequests( VOID ) /*++
Routine Description:
Cleanup WP_CONTEXT data structures
Arguments:
None
Return Value:
HRESULT
--*/ { HRESULT hr = NO_ERROR;
//
// Stop monitoring the number of pending requests since
// it's time to shutdown
//
UL_NATIVE_REQUEST::StopPendingRequestsMonitor(); //
// If we want to shut-down immediately, then close the AppPool handle now
//
if (_fImmediateShutdown) { _ulAppPool.Cleanup(); }
//
// Wait for requests to drain away. If they were pending
// UlReceiveHttpRequest, they will complete with error. If they were
// already processing, then we wait for them to finish
//
hr = UL_NATIVE_REQUEST::ReleaseAllWorkerRequests(); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error draining UL_NATIVE_REQUESTs. hr = %x\n", hr ));
return hr; }
//
// If we want to shut-down gracefully, then close the AppPool handle now
//
if (!_fImmediateShutdown) { _ulAppPool.Cleanup(); }
//
// Wait for outstanding disconnect requests (i.e. UlWaitForDisconnect)
// to drain
//
UL_DISCONNECT_CONTEXT::WaitForOutstandingDisconnects();
//
// Send WAS final counter data before shutting down
//
hr = _WpIpm.HandleCounterRequest();
return hr; }
VOID WP_CONTEXT::RunMainThreadLoop( VOID ) /*++
Routine Description:
Wait for the shutdown event
Arguments:
None
Return Value:
None
--*/ { do { DWORD result;
result = WaitForSingleObject( _hDoneEvent, INFINITE );
DBG_ASSERT( result == WAIT_OBJECT_0 );
} while ( !_fShutdown );
}
HANDLE WP_CONTEXT::GetAndLockAsyncHandle( VOID ) /*++
Routine Description:
Read locks and returns the async handle.
Arguments:
None
Return Value:
HANDLE
--*/ { return _ulAppPool.QueryAndLockHandle(); }
HRESULT WP_CONTEXT::UnlockAsyncHandle( VOID ) /*++
Routine Description:
Read unlocks the async handle.
Arguments:
None
Return Value:
HRESULT
--*/ { return _ulAppPool.UnlockHandle(); }
|