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

820 lines
16 KiB
C++

/*++
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 fUnused
)
/*++
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 = _BusySignal;
InterlockedIncrement( (PLONG)&_CurrentIdleTick );
_BusySignal = 0;
if ( !BusySignal && _CurrentIdleTick >= _IdleTime )
{
DBGPRINTF(( DBG_CONTEXT,
"Idle time reached. Send shutdown message to WAS.\n" ));
g_pwpContext->SendMsgToAdminProcess( IPM_WP_IDLE_TIME_REACHED );
}
}
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 );
}
WP_CONTEXT::WP_CONTEXT(
VOID
) : _hDoneEvent( NULL ),
_pConfigInfo( NULL ),
_fShutdown( FALSE ),
_pIdleTimer( NULL )
{
}
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;
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;
}
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;
}
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;
}
//
// 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;
}
}
//
// Setup all async completions on data channel handle to go thru W3TP
//
if (!ThreadPoolBindIoCompletionCallback( _ulAppPool.QueryHandle(),
OverlappedCompletionRoutine,
0 ))
{
hr = HRESULT_FROM_WIN32(GetLastError());
DBGPRINTF(( DBG_CONTEXT,
"Failed to associate handle with thread pool. hr = %x\n",
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(" - wp1234567890 - 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" - wp%lu - %02u/%02u %02u:%02u:%02u",
_pConfigInfo->QueryNamedPipeId(),
fields.Month,
fields.Day,
fields.Hour,
fields.Minute,
fields.Second
);
hr = strTitle.Append( buffer2 );
}
if (SUCCEEDED(hr))
{
SetConsoleTitleW( strTitle.QueryStr() );
}
}
return NO_ERROR;
Finished:
//
// Terminate recycler object
// Dependency warning: _WpIpm must still be valid
//
if ( fWpRecyclerInit )
{
WP_RECYCLER::Terminate();
}
if ( fIpmInit )
{
_WpIpm.Terminate();
}
if ( _pIdleTimer != NULL )
{
delete _pIdleTimer;
_pIdleTimer = NULL;
}
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 ));
}
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
--*/
{
_fImmediateShutdown = fImmediate;
if ( !InterlockedCompareExchange((LONG *)&_fShutdown, TRUE, FALSE ) )
{
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;
}
//
// 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();
//
// Stop IPM
//
if ( _pConfigInfo->QueryRegisterWithWAS() )
{
hr = _WpIpm.Terminate();
if ( FAILED( hr ) )
{
DBGPRINTF(( DBG_CONTEXT,
"Failed to shutdown IPM. hr = %x\n",
hr ));
}
}
//
// 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;
//
// 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 );
}