/*++ Copyright (c) 1999 Microsoft Corporation Module Name: wpipm.cxx Abstract: Contains the WPIPM class that handles communication with the admin service. WPIPM responds to pings, and tells the process when to shut down. Author: Michael Courage (MCourage) 22-Feb-1999 Revision History: --*/ #include #include "dbgutil.h" #include "wpipm.hxx" #include "RwpFunctions.hxx" extern PFN_ULATQ_COLLECT_PERF_COUNTERS g_pfnCollectCounters; /** * * Routine Description: * * Initializes WPIPM. * * Arguments: * * pWpContext - pointer to the wp context (so we can tell it to shutdown) * * Return Value: * * HRESULT */ HRESULT WP_IPM::Initialize( WP_CONTEXT * pWpContext ) { HRESULT hr = S_OK; m_pWpContext = pWpContext; DWORD dwId = GetCurrentProcessId(); // // create pipe // hr = IPM_MESSAGE_PIPE::CreateIpmMessagePipe(this, pWpContext->QueryConfig()->QueryNamedPipeId(), FALSE, // not server side NULL, // security descriptor &m_pPipe); if (FAILED(hr)) { goto exit; } // // Send the real pid over the pipe // if(!RWP_IPM_BadParamTest(RWP_IPM_OP_GETPID, &hr, m_pPipe)) { hr = m_pPipe->WriteMessage(IPM_OP_GETPID, sizeof(dwId), &dwId); } if (FAILED(hr)) { goto exit; } hr = S_OK; exit: if (FAILED(hr)) { Terminate(); } return hr; } /** * * Routine Description: * * Terminates WPIPM. * * If the message pipe is open this function will disconnect it * and wait for the pipe's disconnection callback. * * Arguments: * * None. * * Return Value: * * HRESULT */ HRESULT WP_IPM::Terminate( VOID ) { if (m_pPipe) { m_pPipe->DestroyIpmMessagePipe(); // pipe deletes itself m_pPipe = NULL; } m_pWpContext = NULL; return S_OK; } /** * * * Routine Description: * * This is a callback from the message pipe that means * the pipe has received a message. * * We decode the message and respond appropriately. * * Arguments: * * pPipeMessage - the message that we received * * Return Value: * * HRESULT * */ VOID WP_IPM::AcceptMessage( IN const IPM_MESSAGE * pPipeMessage ) { HRESULT hr = NO_ERROR; BOOL fRet = FALSE; switch (pPipeMessage->GetOpcode()) { case IPM_OP_PING: // // Pings must go through the same mechanism that requests go through // to verify that requests are being picked off of the completion port // fRet = ThreadPoolPostCompletion(0, HandlePing, (LPOVERLAPPED)this); if (FALSE == fRet) { hr = HRESULT_FROM_WIN32(GetLastError()); DBGPRINTF((DBG_CONTEXT, "Posting completion for ping handling failed")); break; } break; case IPM_OP_SHUTDOWN: hr = HandleShutdown( *( reinterpret_cast( pPipeMessage->GetData() ) ) ); break; case IPM_OP_REQUEST_COUNTERS: hr = HandleCounterRequest(); break; case IPM_OP_PERIODIC_PROCESS_RESTART_PERIOD_IN_MINUTES: DBG_ASSERT( pPipeMessage->GetData() != NULL ); hr = WP_RECYCLER::StartTimeBased( *( reinterpret_cast( pPipeMessage->GetData() ) ) ); hr = NO_ERROR; break; case IPM_OP_PERIODIC_PROCESS_RESTART_MEMORY_USAGE_IN_KB: { DBG_ASSERT( pPipeMessage->GetData() != NULL ); // there are 2 DWORDS sent with memory based recycling // first is Max Virtual Memory, second is Max Private Bytes DWORD dwMaxVirtualMemoryKbUsage = *( reinterpret_cast( pPipeMessage->GetData() ) ); DWORD dwMaxPrivateBytesKbUsage = *( reinterpret_cast( pPipeMessage->GetData() ) + 1 ); hr = WP_RECYCLER::StartMemoryBased( dwMaxVirtualMemoryKbUsage, dwMaxPrivateBytesKbUsage ); hr = NO_ERROR; break; } case IPM_OP_PERIODIC_PROCESS_RESTART_SCHEDULE: DBG_ASSERT( pPipeMessage->GetData() != NULL ); hr = WP_RECYCLER::StartScheduleBased( ( reinterpret_cast( pPipeMessage->GetData() ) ) ); hr = NO_ERROR; break; default: DBG_ASSERT(FALSE); hr = E_FAIL; break; } return; } /** * * Routine Description: * * This is a callback from the message pipe that means * the pipe has been connected and is ready for use. * * Arguments: * * None. * * Return Value: * * VOID */ VOID WP_IPM::PipeConnected( VOID ) { return; } /** * * Routine Description: * * This is a callback from the message pipe that means * the pipe has been disconnected and you won't be receiving * any more messages. * * Tells WPIPM::Terminate that it's ok to exit now. * * Arguments: * * hr - the error code associated with the pipe disconnection * * Return Value: * * VOID */ VOID WP_IPM::PipeDisconnected( IN HRESULT hr ) { if (FAILED(hr)) { DBGPRINTF( (DBG_CONTEXT, "PipeDisconnected with hr ( %d).\n", hr)); } // // All sorts of miscreant shutdown behaviors (for testing) // if (RwpBehaviorExhibited = RWP_Shutdown_Behavior(&hr)) return; // // If the pipe disappears out from under us, WAS has probably orphaned // us, initiate fast shutdown of this worker process. // if (!m_pWpContext->IsInShutdown() && hr != HRESULT_FROM_WIN32(ERROR_OPERATION_ABORTED) && IsDebuggerPresent()) { DBG_ASSERT( !"w3wp.exe is getting orphaned" ); } m_pWpContext->IndicateShutdown( TRUE ); return; } /** * * Routine Description: * * This is a callback from the message pipe that means * that the pipe received an invalid message. * Therefore, we signal to shutdown. * * Arguments: * * VOID * * Return Value: * * VOID */ VOID WP_IPM::PipeMessageInvalid( VOID ) { return PipeDisconnected(HRESULT_FROM_WIN32(ERROR_INVALID_DATA)); } /** * * Routine Description: * * Handles the ping message. Sends the ping response message. * * Arguments: * * None. * * Return Value: * * HRESULT */ //static VOID WP_IPM::HandlePing( DWORD, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpOverlapped ) { HRESULT hr = NO_ERROR; if (0 != dwNumberOfBytesTransferred) { DBG_ASSERT(0 == dwNumberOfBytesTransferred); return; } DBG_ASSERT(NULL != lpOverlapped); WP_IPM * pThis = (WP_IPM*) lpOverlapped; DBG_ASSERT(pThis->m_pPipe); DBGPRINTF( (DBG_CONTEXT, "Handle Ping\n\n")); // If we're supposed to do this test, just do it here. Don't care about result RWP_IPM_BadParamTest(RWP_IPM_OP_INVALID, &hr, pThis->m_pPipe); RWP_IPM_BadParamTest(RWP_IPM_OP_PING_REPLY, &hr, pThis->m_pPipe); // Even if we're testing this opcode, just do regular ping response so the app pool // doesn't get shut down by WAS if (RWP_NO_MISBEHAVE == RWP_Ping_Behavior(&hr, pThis->m_pPipe)) { hr = pThis->m_pPipe->WriteMessage( IPM_OP_PING_REPLY, // ping reply opcode 0, // no data to send NULL // pointer to no data ); if ( FAILED ( hr ) ) { DBGPRINTF( (DBG_CONTEXT, "Failed to respond to ping\n\n")); goto exit; } } // // if we are not healthy then we need to to ask WAS to // shut us down. // if ( !( g_pwpContext->GetUnhealthy())) { DBGPRINTF( (DBG_CONTEXT, "Requesting shutdown due to isapi reporting unhealthiness\n\n")); hr = pThis->SendMsgToAdminProcess( IPM_WP_RESTART_ISAPI_REQUESTED_RECYCLE ); if ( FAILED ( hr ) ) { DBGPRINTF( (DBG_CONTEXT, "Failed telling WAS to shut us down\n\n")); goto exit; } } exit: return; } /** * * Routine Description: * * Handles the counter request message. * * Arguments: * * None. * * Return Value: * * HRESULT */ HRESULT WP_IPM::HandleCounterRequest( VOID ) { DBGPRINTF( (DBG_CONTEXT, "Handle Counter Request\n\n")); HRESULT hr; PBYTE pCounterData; DWORD dwCounterData; DBG_ASSERT ( m_pPipe ); if (FAILED(hr = g_pfnCollectCounters(&pCounterData, &dwCounterData))) { DBGPRINTF( (DBG_CONTEXT, "Didn't collect counters\n\n")); return hr; } if(!RWP_IPM_BadParamTest(RWP_IPM_OP_SEND_COUNTERS, &hr, m_pPipe)) { // If we're not testing this opcode, just do regular valid call hr = m_pPipe->WriteMessage(IPM_OP_SEND_COUNTERS, // ping reply opcode dwCounterData, // no data to send pCounterData); // pointer to no data } return hr; } /** * * Routine Description: * * * Handles the shutdown message. Shuts down the process * * Arguments: * * None. * * Return Value: * * HRESULT */ HRESULT WP_IPM::HandleShutdown( BOOL fDoImmediate ) { HRESULT hr = S_OK; DBGPRINTF( (DBG_CONTEXT, "Handle ******************** Shutdown\n\n")); // // All sorts of miscreant shutdown behaviors (for testing) // if (RwpBehaviorExhibited = RWP_Shutdown_Behavior(&hr)) return (hr); m_pWpContext->IndicateShutdown( fDoImmediate ); return hr; } /** * * Routine Description: * * Sends the message to indicate the worker process has either finished * initializing or has failed to initialize. * * Arguments: * * HRESULT indicating success/failure of initialization * * Return Value: * * HRESULT */ HRESULT WP_IPM::SendInitCompleteMessage( HRESULT hrToSend ) { HRESULT hr; if ( m_pPipe ) { if(!RWP_IPM_BadParamTest(RWP_IPM_OP_HRESULT, &hr, m_pPipe)) { // If we're not testing this opcode, just do regular valid call hr = m_pPipe->WriteMessage( IPM_OP_HRESULT, // opcode sizeof( hrToSend ), // data length reinterpret_cast( &hrToSend ) // pointer to data ); } return hr; } // if the pipe did not exist then we started up // without the IPM, probably we are attempting // to run without WAS support. ( from the cmd line ) return S_OK; } /** * * Routine Description: * * Sends the message to indicate the worker process has reach certain state. * Main use is in shutdown. See IPM_WP_SHUTDOWN_MSG for reasons. * * Arguments: * * None. * * Return Value: * * HRESULT */ HRESULT WP_IPM::SendMsgToAdminProcess( IPM_WP_SHUTDOWN_MSG reason ) { HRESULT hr = S_OK; if (m_pPipe) { // // All sorts of miscreant process rotation behaviors (for testing) // if (RwpBehaviorExhibited = RWP_Rotation_Behavior(&hr, m_pPipe)) return (hr); if(!RWP_IPM_BadParamTest(RWP_IPM_OP_WORKER_REQUESTS_SHUTDOWN, &hr, m_pPipe)) { // If we're not testing this opcode, just do regular valid call hr = m_pPipe->WriteMessage( IPM_OP_WORKER_REQUESTS_SHUTDOWN, // sends message indicate shutdown sizeof(reason), // no data to send (BYTE *)&reason // pointer to no data ); } } return S_OK; }