/*++ 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(); }