/*----------------------------------------------------------------------------- Copyright (c) 1995-1997 Microsoft Corporation Module Name : wamexec.cxx Abstract: This module executes a wam request Author: David Kaplan ( DaveK ) 11-Mar-1997 Lei Jin (leijin) 24-Apr-1997 (WamInfo & WamDictator) Environment: User Mode - Win32 Project: W3 services DLL -----------------------------------------------------------------------------*/ #include #include "wamexec.hxx" #include "waminfo.hxx" #include "wamreq.hxx" // MIDL-generated include files #include "wam.h" #include "wam_i.c" #include #include #include "mtacb.h" #include "gip.h" #include #include "wrcstids.hxx" #include "wrcfixed.hxx" #define SZ_FAILED_OOP_REQUEST_LOG_MESSAGE \ "Out-of-process ISAPI extension request failed." #define SZ_FAILED_OOP_EXCEEDED_CRASH_LOG_MESSAGE \ "Out-of-process ISAPI extension has exceeded crash limit." /*----------------------------------------------------------------------------- Globals -----------------------------------------------------------------------------*/ extern PFN_INTERLOCKED_COMPARE_EXCHANGE g_pfnInterlockedCompareExchange; // // This number is in ms, 10 (min) * 60 (sec/min) * 1000 (ms/s). // DCOM/RPC level holds some reference(up to 2) to a WAM_REQUESTs when an OOP MTS process crashes, // In order to clean the Wam_Request completely, w3svc needs to wait for 10 min(enough for 6 min DCOM // refenrence timeout). // #define DEFAULT_CLEANUP_WAIT 600000 /*----------------------------------------------------------------------------- WamInfo State Description State Valid(Can Serving requests) WIS_START F (sould not get into this state at runtime) WIS_RUNNING T WIS_REPAIR T (Yes, although requests is going to be waiting or rejected). WIS_PAUSE F (NYI) WIS_CPUPAUSE T WIS_ERROR T (Yes, the requests is going to be rejected). WIS_SHUTDOWN T WIS_END F (should not get into this state at runtime) WIS_MAX_STATE F (should not get into this state at runtime) Please refer to waminfo.hxx for detailed description of waminfo state. -----------------------------------------------------------------------------*/ BOOL CWamInfo::m_rgValidState[WIS_MAX_STATE] = { FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE }; #define WAMINFO_SIGNATURE (DWORD)'FIMW' #define FREED_WAMINFO_SIGNATURE (DWORD)'fIMW' /*----------------------------------------------------------------------------- CWamInfo::CWamInfo Constructor Arguments: pstrMetabasePath [in] a pointer to Metabase Path. fInProcess [in] in-proc/out-proc // remote is not a choice fEnableTryExcept [in] enable try/except flag clsidWam [in] the WAM CLSID -----------------------------------------------------------------------------*/ CWamInfo::CWamInfo ( const STR &strMetabasePath, BOOL fInProcess, BOOL fInPool, BOOL fEnableTryExcept, REFGUID clsidWam ) : m_strApplicationPath(strMetabasePath), m_dwSignature(WAMINFO_SIGNATURE), m_pIWam(NULL), m_dwIWamGipCookie( NULL_GIP_COOKIE ), m_cRef(0), m_cCurrentRequests(0), m_cTotalRequests(0), m_dwState(WIS_START), m_fInProcess(fInProcess), m_fEnableTryExcept(fEnableTryExcept), m_fShuttingDown(FALSE), m_pProcessEntry(NULL), m_clsidWam(clsidWam), m_fInPool(fInPool), m_dwRecycleSchedulerCookie(0), m_fRecycled(0), m_cMaxRequests(0) { InitializeListHead(&leProcess); INITIALIZE_CRITICAL_SECTION(&m_csRecycleLock); } /*----------------------------------------------------------------------------- CWamInfo::~CWamInfo Destructor -----------------------------------------------------------------------------*/ CWamInfo::~CWamInfo ( void ) { DeleteCriticalSection(&m_csRecycleLock); m_dwSignature = FREED_WAMINFO_SIGNATURE; DBG_ASSERT(m_cRef == 0); DBG_ASSERT(m_cCurrentRequests == 0); } HRESULT CWamInfo::PreProcessWamRequest ( IN WAM_REQUEST* pWamRequest, IN HTTP_REQUEST* pHttpRequest, OUT IWam** ppIWam, OUT BOOL* pfHandled ) { IWam *pIWam = NULL; DBG_ASSERT( m_dwIWamGipCookie != NULL_GIP_COOKIE ); if( FAILED( g_GIPAPI.Get( m_dwIWamGipCookie, IID_IWam, (void **)&pIWam ))) { // // bad news - GIP refused to provide a pointer // we'll let higher levels to deal with it and return NULL here // pIWam = NULL; } *ppIWam = pIWam; return NOERROR; } HRESULT CWamInfo::PreProcessAsyncIO ( IN WAM_REQUEST * pWamRequest, OUT IWam ** ppIWam ) { IWam *pIWam = NULL; DBG_ASSERT( m_dwIWamGipCookie != NULL_GIP_COOKIE ); if( FAILED( g_GIPAPI.Get( m_dwIWamGipCookie, IID_IWam, (void **)&pIWam ))) { // // bad news - GIP refused to provide a pointer // we'll let higher levels to deal with it and return NULL here // pIWam = NULL; } *ppIWam = pIWam; return NOERROR; } /*----------------------------------------------------------------------------- CWamInfo::ProcessWamRequest Process a Wam Request(of this application path). The request must ask for the same application path with this WamInfo. This function will create a wam request, process the request and finish the request. Arguments: pHttpRequest [in] pointer to HTTP Request pExec [in] pointer to Exec descriptor pstrPath, [in] physical path of the dll??? pfHandled, [out] pfFinished [out] Return: HRESULT -----------------------------------------------------------------------------*/ HRESULT CWamInfo::ProcessWamRequest ( HTTP_REQUEST * pHttpRequest, EXEC_DESCRIPTOR * pExec, const STR * pstrPath, BOOL * pfHandled, BOOL * pfFinished ) { HRESULT hr = NOERROR; WAM_REQUEST * pWamRequest = NULL; IWam * pIWam = NULL; if ( !FCurrentStateValid() || m_fShuttingDown) { if ( QueryState() == WIS_CPUPAUSE ) { // // Since WAM_INFO is going to send some message to the browser, we need // to set pfHandled = TRUE here. // *pfHandled = TRUE; if ( !pExec->IsChild() ) { pHttpRequest->SetLogStatus( HT_SVC_UNAVAILABLE, ERROR_NOT_ENOUGH_QUOTA ); pHttpRequest->Disconnect(HT_SVC_UNAVAILABLE, IDS_SITE_RESOURCE_BLOCKED, TRUE, pfFinished); } Dereference(); // // Since we already called Disconnect, WAM_INFO should return TRUE to high level. // return NOERROR; } IF_DEBUG( WAM_ISA_CALLS ) { DBGPRINTF(( DBG_CONTEXT , "CWamInfo(%08x) shutting down. " "Http-request(%08x) will be aborted.\n" , this , pHttpRequest )); } Dereference(); return HRESULT(ERROR_SERVER_DISABLED); } // Create the wam req NOTE CreateWamRequestInstance addref's hr = CreateWamRequestInstance( pHttpRequest, pExec, pstrPath, this, &pWamRequest); if ( FAILED(hr)) { DBGPRINTF(( DBG_CONTEXT, "WAMInfo::ProcessWamRequest: CreateWamRequestInstance failed, hr = %08x\n", hr )); // The Dereference balances the reference in FindWamInfo. Dereference(); goto LExit; } DBG_ASSERT(pWamRequest != NULL); // Temporarily allow the number of pool threads to grow while we're processing the request AtqSetInfo( AtqIncMaxPoolThreads, 0 ); InterlockedIncrement(&m_cTotalRequests); InterlockedIncrement(&m_cCurrentRequests); IF_DEBUG( WAM_ISA_CALLS ) DBGPRINTF(( DBG_CONTEXT, "CWamInfo 0x%08x: serviced %d requests of maximum %d.\r\n", this, m_cTotalRequests, m_cMaxRequests )); if ( m_cMaxRequests && m_cMaxRequests <= m_cTotalRequests ) { Recycle( 0 ); } hr = PreProcessWamRequest(pWamRequest, pHttpRequest, &pIWam, pfHandled); if (SUCCEEDED(hr)) { DBG_ASSERT(pIWam != NULL); hr = DoProcessRequestCall( pIWam, pWamRequest, pfHandled ); pIWam->Release(); // // got rid of it in Avalanche // *pfFinished = FALSE; AtqSetInfo( AtqDecMaxPoolThreads, 0 ); if( WIN32_FROM_HRESULT( hr ) == ERROR_ACCESS_DENIED ) { DBGPRINTF(( DBG_CONTEXT,"Process wam request-access denied\n",hr)); pWamRequest->SetDeniedFlags( SF_DENIED_RESOURCE ); pWamRequest->SetExpectCleanup( TRUE ); } else if( RPC_S_CALL_FAILED == WIN32_FROM_HRESULT(hr) || RPC_S_CALL_FAILED_DNE == WIN32_FROM_HRESULT(hr) || RPC_S_SERVER_UNAVAILABLE == WIN32_FROM_HRESULT(hr) ) { // 1> If the OOP call failed during the call, RPC returns RPC_S_CALL_FAILED. // ie. OOP process crashed during the call // 2> If OOP process crashed before the call, RPC returns RPC_S_CALL_FAILED_DNE. // // Subsequest requests to the crashed OOP Process will get RPC_S_SERVER_UNAVAILABLE. // PostProcessRequest(hr, pWamRequest); } else { DBG_ASSERT( HRESULT_FACILITY(hr) != FACILITY_RPC ); // Success pWamRequest->SetExpectCleanup( TRUE ); } } else { if (TRUE == *pfHandled) { DBG_ASSERT(pIWam == NULL && hr == HRESULT_FROM_WIN32(RPC_S_CALL_FAILED)); hr = NOERROR; } AtqSetInfo( AtqDecMaxPoolThreads, 0 ); } InterlockedDecrement(&m_cCurrentRequests); LExit: // NOTE this release balances with addref in CreateWamRequestInstance() if (pWamRequest) { pWamRequest->Release(); pWamRequest = NULL; } return hr; }//CWamInfo::ProcessWamRequest HRESULT CWamInfo::DoProcessRequestCall ( IN IWam * pIWam, IN WAM_REQUEST * pWamRequest, OUT BOOL * pfHandled ) { return pIWam->ProcessRequest( pWamRequest, pWamRequest->CbWrcStrings( m_fInProcess ), NULL, pfHandled ); } /*----------------------------------------------------------------------------- CWamInfo::ProcessAsyncIO ProcessAsyncIO. -----------------------------------------------------------------------------*/ HRESULT CWamInfo::ProcessAsyncIO ( WAM_REQUEST* pWamRequest, #ifdef _WIN64 UINT64 pWamExecInfoIn, #else ULONG_PTR pWamExecInfoIn, #endif DWORD dwStatus, DWORD cbWritten, LPBYTE lpOopReadData // Default == NULL ) { HRESULT hr = NOERROR; IWam *pIWam = NULL; hr = PreProcessAsyncIO(pWamRequest, &pIWam); if (SUCCEEDED(hr)) { DBG_ASSERT(pIWam != NULL); if( lpOopReadData == NULL ) { hr = pIWam->ProcessAsyncIO( pWamExecInfoIn, dwStatus, cbWritten ); } else { DBG_ASSERT( !m_fInProcess ); hr = pIWam->ProcessAsyncReadOop( pWamExecInfoIn, dwStatus, cbWritten, lpOopReadData ); } pIWam->Release(); if( WIN32_FROM_HRESULT( hr ) == ERROR_ACCESS_DENIED ) { DBGPRINTF(( DBG_CONTEXT,"Process wam request-access denied\n",hr)); pWamRequest->SetDeniedFlags( SF_DENIED_RESOURCE ); } PostProcessRequest(hr, pWamRequest); } return hr; }//CWamInfo::ProcessAsyncIO /*----------------------------------------------------------------------------- CWamInfo::GetStatistics This code is used internally for iisprobe.dll only. -----------------------------------------------------------------------------*/ HRESULT CWamInfo::GetStatistics ( DWORD Level, LPWAM_STATISTICS_INFO pWamStatsInfo ) { IWam *pIWam = NULL; HRESULT hr; // // both case - go through GIP // DBG_ASSERT(m_dwIWamGipCookie != NULL_GIP_COOKIE); hr = g_GIPAPI.Get( m_dwIWamGipCookie, IID_IWam, (void **) &pIWam ); if( SUCCEEDED( hr ) ) { hr = pIWam->GetStatistics(Level, pWamStatsInfo); pIWam->Release(); } return hr; } // CWamInfo::GetStatistics // // aux class, used temporarily by WamInfo::CreateWamInstance() // struct WAM_CREATOR { REFCLSID m_clsidWam; DWORD m_dwContext; IWam * m_pIWam; DWORD m_dwIWamCookie; BOOL m_fOutOfProc; WAM_CREATOR( REFCLSID clsidWam, DWORD dwContext, BOOL fOutOfProc ); }; // // initialize WAM_CREATOR instance // WAM_CREATOR::WAM_CREATOR( REFCLSID clsidWam, DWORD dwContext, BOOL fOutOfProc ) : m_clsidWam( clsidWam ), m_dwContext( dwContext ), m_pIWam( 0 ), m_dwIWamCookie( 0 ), m_fOutOfProc( fOutOfProc ) { } // // this conforms to MTA callback format // HRESULT __stdcall WamCreatorCallback(void *v1, void *v2) { HRESULT hr; WAM_CREATOR * pwc = (WAM_CREATOR *) v1; hr = CoCreateInstance( pwc->m_clsidWam, NULL, pwc->m_dwContext, IID_IWam, (void **)&pwc->m_pIWam ); DBGPRINTF( (DBG_CONTEXT, "CoCreated WAM: %x(%x)\n", pwc->m_pIWam, hr ) ); if(SUCCEEDED(hr) && pwc->m_fOutOfProc) { // // BUG: 609457 // Set the proxy blanket so that the client can't // impersonate us. // hr = CoSetProxyBlanket( pwc->m_pIWam, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IDENTIFY, COLE_DEFAULT_AUTHINFO, EOAC_DEFAULT ); if ( FAILED( hr ) ) { DBGPRINTF((DBG_CONTEXT, "Failed to CoSetProxyBlanket WAM.\r\n")); } } if(SUCCEEDED(hr)) { // // OOP case needs GIPs // hr = g_GIPAPI.Register(pwc->m_pIWam, IID_IWam, &pwc->m_dwIWamCookie); if(FAILED(hr)) { DBGPRINTF( (DBG_CONTEXT, "Failed to register w GIP:%x (%x)\n", pwc->m_pIWam, hr ) ); pwc->m_pIWam->Release(); pwc->m_pIWam = NULL; }else{ DBGPRINTF( (DBG_CONTEXT, "Registered w GIP OK\n" ) ); } } return hr; } /*----------------------------------------------------------------------------- CWamInfo::CreateWamInstance Creates WAM instance. For complete detail, see BUG #137647. In short, the purpose of this is to make sure that CoCreateInstance() is called from MTA thread and not from one of ATQ threads, converted to STA by a random ISAPI extension. The real work is done in WamCreatorCallback() above. Returns: HRESULT of CoCreateInstance(), and, in OOP case, HRESULT of GIPAPI.Register -----------------------------------------------------------------------------*/ HRESULT CWamInfo::CreateWamInstance( DWORD dwContext ) { HRESULT hr = 0; WAM_CREATOR wc( m_clsidWam, CLSCTX_ALL, !m_fInProcess ); // // do the actual work on MTA thread // hr = CallMTACallback( WamCreatorCallback, &wc, NULL ); // // store resulting IWAM pointer and GIP cookie // m_pIWam = (IWam *) wc.m_pIWam; m_dwIWamGipCookie = wc.m_dwIWamCookie; return hr; } /*----------------------------------------------------------------------------- CWamInfo::Init Create the WAM object. One-to-One mapping between a WamInfo and a Wam. Arguments: None. Return: HRESULT -----------------------------------------------------------------------------*/ HRESULT CWamInfo::Init ( WCHAR* wszPackageId, DWORD pidInetInfo ) { HRESULT hr = NOERROR; IWam * pIWam = NULL; // If WamInfo on start or // WamInfo is out of proc and it is in either repair or cuppause sate. // Init the WamInfo DBG_ASSERT(((m_dwState == WIS_REPAIR || m_dwState == WIS_CPUPAUSE) && !m_fInProcess) || m_dwState == WIS_START ); DWORD dwContext = ((m_fInProcess) ? CLSCTX_INPROC_SERVER : CLSCTX_LOCAL_SERVER ); IF_DEBUG( ERROR ) { Print(); } DBG_ASSERT( NULL == m_pIWam); DBGPRINTF( (DBG_CONTEXT, "Creating WAM Instance dwContext = %x\n", dwContext ) ); hr = CreateWamInstance( dwContext ); //DBGPRINTF( (DBG_CONTEXT, "Created WAM Instance %x (%x)\n", m_pIWam, hr ) ); if (SUCCEEDED(hr)) { DWORD pidWamProcess = 0; // Use GIP in inproc case as well. DBG_ASSERT( m_dwIWamGipCookie != NULL_GIP_COOKIE); hr = g_GIPAPI.Get( m_dwIWamGipCookie, IID_IWam, (void **) &pIWam ); if( SUCCEEDED( hr ) ) { hr = pIWam->InitWam( m_fInProcess, m_fInPool, m_fEnableTryExcept, W3PlatformType, &pidWamProcess ); pIWam->Release(); } if( SUCCEEDED(hr) && !m_fInProcess ) { // // Under some circumstances com svcs my get hosed. This causes // object activation to happen in process even if the object // is registered to be launched in the surrogate. In order to // prevent inetinfo from AV'ing we want to prevent these // applications from running. // DBG_ASSERT( pidInetInfo != pidWamProcess ); if( pidInetInfo == pidWamProcess ) { // Log the failure. // W3_EVENT_FAIL_OOP_ACTIVATION - in W3SVC.dll const CHAR * pszEventLog[2]; CHAR szAppId[40]; *szAppId = 0; WideCharToMultiByte( CP_ACP, 0, wszPackageId, -1, szAppId, 40, NULL, NULL ); pszEventLog[0] = szAppId; pszEventLog[1] = m_strApplicationPath.QueryStr(); g_pInetSvc->LogEvent( W3_EVENT_FAIL_OOP_ACTIVATION, 2, pszEventLog, 0 ); // We need to return an error code to prevent further // processing. Since this will get looked up and event // logged, by the WAM_DICTATOR we want to return something // innocuous, but understandable. hr = W3_EVENT_FAIL_OOP_ACTIVATION; } else { // // Schedule recycling if configured to do so // if ( m_dwRecycleTime ) { Recycle( m_dwRecycleTime * 60 * 1000 ); } } } if (SUCCEEDED(hr)) { m_pProcessEntry = g_pWamDictator->m_PTable.AddWamInfoToProcessTable( this, wszPackageId, pidWamProcess); if (m_pProcessEntry != NULL) { // Make WamInfo Alive. This reference is balanced // by CWamInfo::Uninit if the CWamInfo has an associated // process entry. Reference(); DBGPRINTF(( DBG_CONTEXT, "New WamInfo(%08x))\n", this)); } else { DWORD err = GetLastError(); DBGPRINTF(( DBG_CONTEXT, "Dup Handle, error %08x\n", err )); hr = HRESULT_FROM_WIN32( err); } } else { DBGPRINTF(( DBG_CONTEXT, "WAM(%08x)::InitWam() failed, error %08x\n", m_dwIWamGipCookie, hr )); } } else { /* some possible failures: CO_E_NOTINITIALIZED 0x800401f0 REGDB_E_CLASSNOTREG 0x80040154 */ DBG_ASSERT( hr != CO_E_NOTINITIALIZED ); DBG_ASSERT( hr != REGDB_E_CLASSNOTREG ); DBGPRINTF(( DBG_CONTEXT, "CoCreateInstance failed to create WAM, error %08x\n", hr )); DBG_ASSERT( m_pIWam == NULL); } // Rely on the correct call to UnInit to free things up. If uninit // isn't correctly called, then the WAM object is leaked anyway. /* if ( FAILED( hr) && (m_pIWam != NULL)) { m_pIWam->Release(); // release the Wam m_pIWam = NULL; } */ return hr; } // CWamInfo::Init() VOID CWamInfo::NotifyGetInfoForName ( IN LPCSTR pszServerVariable ) { // Only functional oop return; } /*----------------------------------------------------------------------------- CWamInfo::StartShutdown Phase 1 of shutdown process. Return: HRESULT -----------------------------------------------------------------------------*/ HRESULT CWamInfo::StartShutdown ( INT cIgnoreRefs ) { IF_DEBUG( WAM_ISA_CALLS ) DBGPRINTF((DBG_CONTEXT, "CWamInfo::StartShutdown\n")); HRESULT hr = NOERROR; LONG fShuttingDown; fShuttingDown = InterlockedExchange((LPLONG)&m_fShuttingDown, (LONG)TRUE); if ((BOOL)fShuttingDown == FALSE && m_pIWam != NULL) { IWam *pIWam = NULL; DBG_ASSERT( m_dwIWamGipCookie != NULL_GIP_COOKIE ); if( FAILED( g_GIPAPI.Get( m_dwIWamGipCookie, IID_IWam, (void **)&pIWam ))) { // // bad news - GIP refused to provide a pointer // we'll let higher levels to deal with it and return NULL here // pIWam = NULL; } else { hr = pIWam->StartShutdown(); pIWam->Release(); pIWam = NULL; } } LoopWaitForActiveRequests(cIgnoreRefs); return hr; } // CWamInfo::StartShutdown() /*----------------------------------------------------------------------------- CWamInfo::ClearMembers Clears the members that are not cleared by unit, so that it can be reused after a CPU Pause. Return: HRESULT -----------------------------------------------------------------------------*/ void CWamInfo::ClearMembers ( void ) { m_fShuttingDown = FALSE; DBG_ASSERT(m_pIWam == NULL && m_pProcessEntry == NULL && m_dwIWamGipCookie == NULL_GIP_COOKIE ); } /*----------------------------------------------------------------------------- CWamInfo::LoopWaitForActiveRequests Loop wait until all active requests are gone. Upon return from this call, there should be no more active requests for this WamInfo. Return: HRESULT -----------------------------------------------------------------------------*/ void CWamInfo::LoopWaitForActiveRequests ( INT cIgnoreRefs ) { INT cBaseRef = 1 + cIgnoreRefs; // // Allow all other references to WAMINFO to drain away. // This loop is especially important in Unload scenario. // Unload Application while w3svc is still running, a busy application might // still have some outstanding WAMREQUEST unfinished which hold the reference to // this WAMINFO. // // m_cRef should be 1 in normal condition. However, m_cRef could be 0. // TODO: Update Comments. // //cBaseRef = (fIgnoreHashTableRef) ? 2; //while (m_cRef > cBaseRef) while(m_cCurrentRequests > 0) { DBGPRINTF((DBG_CONTEXT, "Still have out-standing reference(%d) to this WamInfo %08x\n", //m_cRef, m_cCurrentRequests, this)); Sleep(20); } } // CWamInfo::LoopWaitForActiveRequests /*----------------------------------------------------------------------------- CWamInfo::UnInit Phase 2 of shutdown process. Unload WAM. Release the WAM object. Also release the Process handle. Return: HRESULT -----------------------------------------------------------------------------*/ HRESULT CWamInfo::UnInit ( void ) { HRESULT hr = NOERROR; DBG_ASSERT( m_dwState == WIS_SHUTDOWN || m_dwState == WIS_CPUPAUSE || m_dwState == WIS_START ); // // If there is a scheduled recycle pending, delete it here. // if ( m_dwRecycleSchedulerCookie && !m_fRecycled ) { RemoveWorkItem( m_dwRecycleSchedulerCookie ); } if (m_pIWam != NULL) { IWam * pIWam = NULL; if ( m_dwIWamGipCookie != NULL_GIP_COOKIE ) { // // use a thread-valid pIWam from GIP // hr = g_GIPAPI.Get( m_dwIWamGipCookie, IID_IWam, (void **)&pIWam ); if( SUCCEEDED( hr ) ) { DBG_ASSERT( pIWam ); pIWam->UninitWam(); pIWam->Release(); } // // get rid of the GIP entry // note - this hr will be the return result // hr = g_GIPAPI.Revoke( m_dwIWamGipCookie ); m_dwIWamGipCookie = NULL_GIP_COOKIE; } // balance the AddRef in CWamInfo::Init. m_pIWam->Release(); m_pIWam = NULL; } // If we got a handle for an Out Of Proc Wam, release it if( m_pProcessEntry != NULL) { // Process entry remains in the table, but this reference is // no longer valid g_pWamDictator->m_PTable.RemoveWamInfoFromProcessTable(this); m_pProcessEntry = NULL; // balance the AddRef in WamInfo::Init. Dereference(); } return hr; } void CWamInfo::Print(void) const { if (m_pProcessEntry) { DBGPRINTF(( DBG_CONTEXT, " WAM_INFO(%08x) Refs=%d; TotalReqs=%d; dwState=%d;" " AppPath=%s;" " WamGipCookie=%d; clsid=" GUID_FORMAT " pidWam=%d; hWam=%08x\n" , this, m_cRef, m_cTotalRequests, m_dwState, m_strApplicationPath.QueryStr(), m_dwIWamGipCookie, GUID_EXPAND( &m_clsidWam), m_pProcessEntry->QueryProcessId(), m_pProcessEntry->QueryProcessHandle() )); } else { DBGPRINTF(( DBG_CONTEXT, " WAM_INFO(%08x) Refs=%d; TotalReqs=%d; dwState=%d;" " AppPath=%s;" " WamGipCookie=%d; clsid=" GUID_FORMAT " No WAM Object\n" , this, m_cRef, m_cTotalRequests, m_dwState, m_strApplicationPath.QueryStr(), m_dwIWamGipCookie, GUID_EXPAND( &m_clsidWam) )); } } // CWamInfo::Print() void CWamInfo::Recycle ( DWORD dwTimeInSec ) { RecycleLock(); if ( m_fRecycled ) { goto Done; } // // If dwTimeInSec is zero, set m_fRecycled to TRUE now. This // will prevent races within the scheduler when a CWamInfo // overshoots it's target number of requests and every request // wants to recycle. // if ( !dwTimeInSec ) { m_fRecycled = TRUE; } // // Do the recycle on a different thread so that the current // request is not delayed. // if ( m_dwRecycleSchedulerCookie ) { if ( ScheduleAdjustTime( m_dwRecycleSchedulerCookie, 0 ) != NO_ERROR ) { // // Too late. We had a previously scheduled thread, // and it's not here anymore. It must have run. // goto Done; } else { goto Done; } } // // This reference is balanced by the callback function or the // zero m_dwRecycleSchedulerCookie check below. // Reference(); m_dwRecycleSchedulerCookie = ScheduleWorkItem( RecycleCallback, this, dwTimeInSec, FALSE ); if ( !m_dwRecycleSchedulerCookie ) { Dereference(); } Done: RecycleUnlock(); } /*----------------------------------------------------------------------------- CWamInfo::QueryKey Query strMetabasePath. Return: HRESULT -----------------------------------------------------------------------------*/ LPCSTR CWamInfo::QueryKey() const { return m_strApplicationPath.QueryStr(); } /*----------------------------------------------------------------------------- COOPWamReqList::COOPWamReqList Constructor for COOPWamReqList -----------------------------------------------------------------------------*/ COOPWamReqList::COOPWamReqList() : m_dwTimeStamp(0), m_fActive(TRUE) { InitializeListHead(&m_leRecoverListLink); InitializeListHead(&m_leOOPWamReqListHead); } /*----------------------------------------------------------------------------- COOPWamReqList::~COOPWamReqList Destructor for COOPWamReqList -----------------------------------------------------------------------------*/ COOPWamReqList::~COOPWamReqList(VOID) { DBG_ASSERT(IsListEmpty(&m_leOOPWamReqListHead)); // Reset link with m_rgOOPWamReqLists... InitializeListHead(&m_leRecoverListLink); } /*----------------------------------------------------------------------------- COOPWamReqList::FTimeToCleanup Check whether it is the time to cleanup the CleanupList. Must meet the condition that the time since the corresponding instance of WAM crashed elapsed is more that 10 min. -----------------------------------------------------------------------------*/ BOOL COOPWamReqList::FTimeToCleanup(DWORD dwCurrentTime) { // // iF THIS WamReqList is an active list, return FALSE immediately, because // an ActiveOne have timestamp = 0, the following check most likely will trigger // a TRUE value. // if (m_fActive) { return FALSE; } // CASE 1 // After the DEFAULT_CLEANUP_WAIT period, it is OK to cleanup this list. // if ((dwCurrentTime > m_dwTimeStamp) && (dwCurrentTime - m_dwTimeStamp) > DEFAULT_CLEANUP_WAIT) { return TRUE; } // CASE 2, the value returned from GetTickCount() gets wraped, needs to take care // the special condition. // if ((dwCurrentTime < m_dwTimeStamp) && ((dwCurrentTime - 0) + (0xFFFFFFFF - m_dwTimeStamp) > DEFAULT_CLEANUP_WAIT)) { return TRUE; } return FALSE; }//CWamInfoOutProc::FTimeToCleanup /*----------------------------------------------------------------------------- CWamInfoOutProc::CWamInfoOutProc Arguments: pstrMetabasePath [in] a pointer to Metabase Path. fInProcess [in] in-proc/out-proc // remote is not a choice fEnableTryExcept [in] enable try/except flag clsidWam [in] the WAM CLSID -----------------------------------------------------------------------------*/ CWamInfoOutProc::CWamInfoOutProc ( IN const STR &strMetabasePath, IN BOOL fInProcess, IN BOOL fInPool, IN BOOL fEnableTryExcept, IN REFGUID clsidWam, IN DWORD dwThreshold, IN PW3_SERVER_INSTANCE pwsiInstance, IN BOOL fJobEnabled, IN DWORD dwPeriodicRestartRequests, IN DWORD dwPeriodicRestartTime, IN DWORD dwShutdownTimeLimit ) : CWamInfo(strMetabasePath, fInProcess, fInPool, fEnableTryExcept, clsidWam), m_fInRepair(FALSE), m_fNoMoreRecovery(FALSE), m_hPermitOOPEvent((HANDLE)NULL), m_dwThreshold(dwThreshold), m_dwWamVersion(0), m_cRecoverList(0), m_idScheduled(0), m_pCurrentListHead(NULL), m_pwsiInstance(pwsiInstance), m_fJobEnabled(fJobEnabled) { InitializeListHead(&m_rgRecoverListHead); INITIALIZE_CRITICAL_SECTION(&m_csList); m_cMaxRequests = dwPeriodicRestartRequests; m_dwRecycleTime = dwPeriodicRestartTime; m_dwShutdownTimeLimit = dwShutdownTimeLimit; } /*----------------------------------------------------------------------------- CWamInfoOutProc::~CWamInfoOutProc Destructor for CWamInfoOutProc -----------------------------------------------------------------------------*/ CWamInfoOutProc::~CWamInfoOutProc ( VOID ) { DBG_ASSERT(IsListEmpty(&m_rgRecoverListHead)); DBG_ASSERT(m_hPermitOOPEvent == (HANDLE)NULL); DeleteCriticalSection(&m_csList); }//CWamInfoOutProc::~CWamInfoOutProc /*----------------------------------------------------------------------------- CWamInfoOutProc::InitOutProc Init the CWamInfoOutProc data structure. Argument: fRepair - TRUE, if Init() is called to recreate an WAM after a OOP crash Return: HRESULT -----------------------------------------------------------------------------*/ HRESULT CWamInfoOutProc::Init ( IN WCHAR* wszPackageId, IN DWORD pidInetInfo ) { HRESULT hr = NOERROR; COOPWamReqList *pList = NULL; // // Init a COOPWamReqList for the first OOP Wam instance. // pList = new COOPWamReqList(); if (NULL == pList) { hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } else { // // Insert to Tail of the LinkList, this action usually is done inside // of a CS, however, since this is the Init time, it is already in a CS, // there could only be one thread doing the Init part of CWamInfoOutProc. // therefore, no CS is needed. // LockList(); InsertTailList(&m_rgRecoverListHead, &(pList->m_leRecoverListLink)); m_pCurrentListHead = &(pList->m_leOOPWamReqListHead); m_cRecoverList++; UnLockList(); } hr = CWamInfo::Init(wszPackageId, pidInetInfo); if (SUCCEEDED(hr)) { if (m_fJobEnabled) { DBG_ASSERT(m_pwsiInstance != NULL && m_pProcessEntry->QueryProcessId() != NULL); m_pwsiInstance->AddProcessToJob(m_pProcessEntry->QueryProcessHandle(), TRUE); } // // m_hPermitOOPEvent can served as m_fInited. // m_hPermitOOPEvent = IIS_CREATE_EVENT( "CWamInfoOutProc::m_hPermitOOPEvent", this, TRUE, TRUE ); if (NULL == m_hPermitOOPEvent) { hr = HRESULT_FROM_WIN32(GetLastError()); } } // // If either CWamInfo::Init or Create Event failed, we need to // remove the pList from CleanupListHead. // if (FAILED(hr) && pList) { RemoveHeadList(&m_rgRecoverListHead); delete pList; pList = NULL; m_cRecoverList--; } return hr; }//CWamInfoOutProc::Init /*----------------------------------------------------------------------------- CWamInfoOutProc::UnInit Phase 2 of shutdown process. Uninit the CWamInfoOutProc data structure. Argument: VOID Return: HRESULT -----------------------------------------------------------------------------*/ HRESULT CWamInfoOutProc::UnInit ( void ) { HRESULT hr = NOERROR; hr = CWamInfo::UnInit(); FinalCleanup(); if (m_hPermitOOPEvent) { CloseHandle(m_hPermitOOPEvent); m_hPermitOOPEvent = (HANDLE)NULL; } return hr; }//CWamInfoOutProc::UnInit /*----------------------------------------------------------------------------- CWamInfoOutProc::ClearMembers Clears the members that are not cleared by unit, so that it can be reused after a CPU Pause. Return: HRESULT -----------------------------------------------------------------------------*/ VOID CWamInfoOutProc::ClearMembers() { CWamInfo::ClearMembers(); m_cRecoverList = 0; } /*----------------------------------------------------------------------------- CWamInfo::LoopWaitForActiveRequests Loop wait until all active requests are gone. Upon return from this call, there should be no more active requests for this WamInfo. Return: HRESULT -----------------------------------------------------------------------------*/ void CWamInfoOutProc::LoopWaitForActiveRequests ( INT cIgnoreRefs ) { INT cCount = 0; BOOL fWaitforActiveReq = TRUE; BOOL fDidForceKill = FALSE; BOOL fForceRequestCleanup; // // No need to unlock/lock the OOPWamRequestList in order to check list is empty or not. // while(m_pCurrentListHead && fWaitforActiveReq) { if (IsListEmpty(m_pCurrentListHead)) { fWaitforActiveReq = FALSE; } Sleep(20); cCount++; // 2 sec = 20 ms * 100 times if (cCount > 100 && fDidForceKill == FALSE) { BOOL fRet; UINT uExitCode = 0; fRet = TerminateProcess(m_pProcessEntry->QueryProcessHandle(), uExitCode); if (!fRet) { DBGPRINTF((DBG_CONTEXT, "Unable to TerminateProcess, error %d\n", GetLastError())); } fDidForceKill = TRUE; // // Walk the list of current WAM_REQUESTs // while(!IsListEmpty(m_pCurrentListHead)) { LockList(); LIST_ENTRY * ple = RemoveHeadList(m_pCurrentListHead); DBG_ASSERT(ple); WAM_REQUEST * pWamRequest = CONTAINING_RECORD( ple, WAM_REQUEST, m_leOOP); // Completely disassociate this wamreq from the list before // leaving the critical section. InitializeListHead( &pWamRequest->m_leOOP ); // Is wamreq about to be cleaned up by itself? fForceRequestCleanup = !( pWamRequest->InterlockedNonZeroAddRef() == 0 ); UnLockList(); if( fForceRequestCleanup ) { // // Invalidate any references that COM might have // CoDisconnectObject( static_cast(pWamRequest), NULL ); // // This triggers WAM_REQUEST destructor // pWamRequest->Release(); } } fWaitforActiveReq = FALSE; } } } // CWamInfoOutProc::LoopWaitForActiveRequests /*----------------------------------------------------------------------------- CWamInfoOutProc::EnterOOPZone Enter the OOP Zone. If there is a WAM crash-recovery in place, the newer coming requests are blocked until the new WAM construction finished. And after the number of crashes reaches a threshold, the function will assigned a NULL pointer to IWam return value. Means WamInfoOutProc will no longer serve any new requests. Parameter: pWamRequest a pointer to WamRequest. pdwWamVersion pointer to a DWORD buffer holds the current WamVersion upon return. fRecord TRUE, add WamRequest to the Current OOPWamReqList. FALSE, no-op. NOTE: EnterOOPZone is called by ProcessAsyncIO(). However, the time a WamRequest in the ProcessAsynIO() call, the WamRequest is already recorded on the OOPWamRequest, therefore, there is no need to add the WamRequest to OOPWamRequest again. -----------------------------------------------------------------------------*/ IWam * CWamInfoOutProc::EnterOOPZone ( WAM_REQUEST * pWamRequest, DWORD *pdwWamVersion, BOOL fRecord ) { IWam * pIWam = NULL; // // In Repair() // We first ReSet the PermitOOPEvent, and then raise the flag. // Therefore, if m_fInRepair, all incoming threads will now wait. // LWait: if (m_fInRepair) { DWORD dwReturn = WaitForSingleObject(m_hPermitOOPEvent, INFINITE); DBG_ASSERT(dwReturn == WAIT_OBJECT_0); } LockList(); if (m_fInRepair) { UnLockList(); goto LWait; } else { if (!FExceedCrashLimit() && !m_fShuttingDown) { if (fRecord) { InsertHeadList(m_pCurrentListHead, &pWamRequest->m_leOOP); } // // get thread-valid IWam (see #122711) // DBG_ASSERT( m_dwIWamGipCookie != NULL_GIP_COOKIE ); if( FAILED( g_GIPAPI.Get( m_dwIWamGipCookie, IID_IWam, (void **)&pIWam ))) { // // bad news - GIP refused to provide a pointer // we'll let higher levels to deal with it and return NULL here // pIWam = NULL; } *pdwWamVersion = m_dwWamVersion; } else { if (fRecord) { InitializeListHead(&pWamRequest->m_leOOP); } pIWam = NULL; *pdwWamVersion = m_dwWamVersion; } } UnLockList(); //DBG_ASSERT(pIWam != NULL); return pIWam; } //CWamInfo::EnterOOPZone /*----------------------------------------------------------------------------- CWamInfoOutProc::LeaveOOPZone Leave OOP zone. take itself from the OOPWamReqList. Parameter: pWamRequest a pointer to a WamRequest that is going to be taken out from OOPWamReqList. fRecord FALSE, NO-OP. Please see comments in EnterOOPZone. FALSE, only used in ProcessAsyncIO. Note: -----------------------------------------------------------------------------*/ VOID CWamInfoOutProc::LeaveOOPZone ( WAM_REQUEST * pWamRequest, BOOL fRecord ) { if (fRecord) { // // Remove the wamreq from the list of active requests // LockList(); if (!IsListEmpty(&pWamRequest->m_leOOP)) { RemoveEntryList(&pWamRequest->m_leOOP); } InitializeListHead(&pWamRequest->m_leOOP); UnLockList(); } return; } HRESULT CWamInfoOutProc::PreProcessWamRequest ( IN WAM_REQUEST* pWamRequest, IN HTTP_REQUEST* pHttpRequest, OUT IWam ** ppIWam, OUT BOOL * pfHandled ) { DWORD dwVersion = 0; HRESULT hrReturn = NOERROR; IWam* pIWam = NULL; BOOL fFinished; DBG_ASSERT(pWamRequest); pIWam = EnterOOPZone(pWamRequest, &dwVersion, TRUE); // stamp the wamreq with wam's current version pWamRequest->SetWamVersion( dwVersion ); if (NULL == pIWam) { // We reach this state only if we exceed the crash limit. DBG_ASSERT(FExceedCrashLimit()); // // write log message for the failed wam request // if ( !pWamRequest->IsChild() ) { pWamRequest->WriteLogInfo( SZ_FAILED_OOP_EXCEEDED_CRASH_LOG_MESSAGE, HT_SERVER_ERROR, RPC_S_CALL_FAILED); pHttpRequest->Disconnect( HT_SERVER_ERROR, IDS_WAM_NOMORERECOVERY_ERROR, TRUE, &fFinished); } DBGPRINTF((DBG_CONTEXT, "Exceed crash limit, Wam Request %08x quits\n", pWamRequest)); *pfHandled = TRUE; hrReturn = HRESULT_FROM_WIN32( RPC_S_CALL_FAILED ); } *ppIWam = pIWam; return hrReturn; }//CWamInfoOutProc::PreProcessRequest HRESULT CWamInfoOutProc::PostProcessRequest ( IN HRESULT hrIn, IN WAM_REQUEST * pWamRequest ) { // 1> If the OOP call failed during the call, RPC returns RPC_S_CALL_FAILED. // ie. OOP process crashed during the call // 2> If OOP process crashed before the call, RPC returns RPC_S_CALL_FAILED_DNE. // // Subsequest requests to the crashed OOP Process will get RPC_S_SERVER_UNAVAILABLE. // if (RPC_S_CALL_FAILED == WIN32_FROM_HRESULT(hrIn) || RPC_S_CALL_FAILED_DNE == WIN32_FROM_HRESULT(hrIn) || RPC_S_SERVER_UNAVAILABLE == WIN32_FROM_HRESULT(hrIn)) { const BOOL F_INRECOVERY = TRUE; const BOOL F_HEALTHY = FALSE; HRESULT hrCleanup; DBGPRINTF(( DBG_CONTEXT , "CWamInfoOutProc(%08x) " "calling CoDisconnectObject on pWamRequest(%08x)\n" , this , pWamRequest )); // // write log message for the failed wam request // if (pWamRequest && !pWamRequest->IsChild() ) { DWORD dwHTHeader = (!m_fShuttingDown) ? HT_SERVER_ERROR : HT_SVC_UNAVAILABLE; pWamRequest->DisconnectOnServerError(dwHTHeader, WIN32_FROM_HRESULT(hrIn)); pWamRequest->WriteLogInfo( SZ_FAILED_OOP_REQUEST_LOG_MESSAGE, dwHTHeader, WIN32_FROM_HRESULT(hrIn)); } hrCleanup = CoDisconnectObject( static_cast(pWamRequest), NULL ); DBG_ASSERT(hrCleanup == S_OK); if (F_INRECOVERY == (BOOL)g_pfnInterlockedCompareExchange( (LONG *)&m_fInRepair, (LONG)F_INRECOVERY, (LONG)F_HEALTHY)) { // Other thread already doing the recovery job. DBGPRINTF((DBG_CONTEXT, "Recovery mode: Other thread is doing recovery job.\n")); //exit this function return hrIn; } // // Try to Repair(call repair function). But other thread might already working on the repairing // Therefore, this thread will return immediately if repairing is in-place. // Forget about pWamRequest->Release(), whoever(thread) doing the repairing job will call // self-destruction on the pWamRequest. // Repair(); if (F_HEALTHY == (BOOL)g_pfnInterlockedCompareExchange( (LONG *)&m_fInRepair, (LONG)F_HEALTHY, (LONG)F_INRECOVERY)) { // Other thread already doing the recovery job. DBG_ASSERT(FALSE); } return hrIn; } return NOERROR; }// CWamInfoOutProc::PreProcessRequest /*----------------------------------------------------------------------------- -----------------------------------------------------------------------------*/ HRESULT CWamInfoOutProc::PreProcessAsyncIO ( IN WAM_REQUEST* pWamRequest, OUT IWam ** ppIWam ) { HRESULT hr = S_OK; IWam* pIWam = NULL; DWORD dwVersion; pIWam = EnterOOPZone(pWamRequest, &dwVersion, FALSE); if (dwVersion > pWamRequest->GetWamVersion()) { // reject the request if current wam version is later // than wam version when request began DBGPRINTF(( DBG_CONTEXT, "CWamInfoOutProc::ProcessAsyncIO - Crashed since request was started. " "Wam Request %08x quits\n", pWamRequest)); hr = HRESULT_FROM_WIN32(RPC_S_CALL_FAILED_DNE); // UNDONE better return code? } else { if (NULL == pIWam) { // We reach this state if we exceed the crash limit // or if we failed to obtain thread-valid pointer from GIP // note that higher levels rely on hr and *don't* check the pointer DBG_ASSERT(FExceedCrashLimit()); DBGPRINTF((DBG_CONTEXT, "Exceed crash limit, Wam Request %08x quits\n", pWamRequest)); hr = HRESULT_FROM_WIN32(RPC_S_CALL_FAILED_DNE); } } *ppIWam = pIWam; return hr; } /*----------------------------------------------------------------------------- CWamInfoOutProc::Repair This is the recovery function. And this function refused to repair the WAM if the crashes exceed a threshold. -----------------------------------------------------------------------------*/ HRESULT CWamInfoOutProc::Repair ( VOID ) { HRESULT hr = NOERROR; COOPWamReqList* pNewList = NULL; BOOL fExceedCrashLimit; // flag indicates we exceed crash limit or not this time // // Step 1. ReSet FinishRecoveryEvent to UnSignaled State. // //DBG_ASSERT( m_pProcessEntry ); if ( m_pProcessEntry ) { m_pProcessEntry->NotifyCrashed(); } ResetEvent(m_hPermitOOPEvent); if (m_dwState == WIS_SHUTDOWN || m_dwState == WIS_CPUPAUSE) { //LockList(); //m_pCurrentListHead = NULL; //UnLockList(); // // Set Event, so that let all other wait threads go // SetEvent(m_hPermitOOPEvent); goto LExit; } // // Make sure m_pCurrentListHead is a valid one. // DBG_ASSERT(m_pCurrentListHead); // // Step 2. Call CleanupAll to release any empty(or timedout) OOPWamReqList // resources. Note: CleanupAll does not touch the list pointed by m_pCurrentListHead. // // CleanupAll(FALSE); DBGPRINTF((DBG_CONTEXT, "Application %s(%08x) currently has %d OOPWamReqLists, %d crashes, m_pCurrentListHead %08x\n", m_strApplicationPath.QueryStr(), this, m_cRecoverList, m_dwWamVersion+1, m_pCurrentListHead )); // // Step 3: Create a new OOPWamReqList, and update m_pCurrentListHead to it. // LockList(); COOPWamReqList *pCleanupList; pCleanupList = CONTAINING_RECORD(m_pCurrentListHead, COOPWamReqList, m_leOOPWamReqListHead); // // if the CurrentList is empty, then, it is OK to remove the OOPWamReqList pointed // by CurrentListHead. Done this step in a CriticalSection. That is, you can only // access OOPWamReqList in a CS. // if (IsListEmpty(m_pCurrentListHead)) { RemoveEntryList(&pCleanupList->m_leRecoverListLink); InterlockedDecrement((LPLONG)&m_cRecoverList); delete pCleanupList; } else { DBG_ASSERT(pCleanupList != NULL); pCleanupList->SetTimeStamp(); } // m_pCurrentListHead is invalid until reassigned a new OOPList later. // // If WamInfoOutProc has already reach the OOPWamReqList resource threshould. // fExceedCrashLimit = FExceedCrashLimit(); // // If we did not exceed the crash limit, then, it is OK to add another OOPWamReqList. // if (!fExceedCrashLimit) { InterlockedIncrement((LPLONG)&m_dwWamVersion); // // Alloc a new CleanupList resource // pNewList = new COOPWamReqList(); if (pNewList != NULL) { InsertTailList(&m_rgRecoverListHead, &(pNewList->m_leRecoverListLink)); InterlockedIncrement((LPLONG)&m_cRecoverList); // // Activate new OOPWamReq List. // m_pCurrentListHead = &pNewList->m_leOOPWamReqListHead; } else { // // Find out exact what error cause new operation to fail // m_pCurrentListHead = NULL; hr = HRESULT_FROM_WIN32(GetLastError()); } } UnLockList(); // // Step 3. Wam is bad. Recreate the wam. // if (SUCCEEDED(hr) && !fExceedCrashLimit) { DBG_ASSERT(m_pCurrentListHead); hr = ReInitWam(); if (FAILED(hr)) { // If ReInitWam failed, then, we can assume there is some thing went really wrong, // We will treate the application as it exceeds the crash limit. LockList(); m_fNoMoreRecovery = TRUE; m_pCurrentListHead = NULL; UnLockList(); DBGPRINTF((DBG_CONTEXT, "Application %s(%08x) Fail to ReInitWam() in Repair, hr = %08x\n", m_strApplicationPath.QueryStr(), this, hr)); } } // Assert that either hr succeeded or list head is null DBG_ASSERT( SUCCEEDED(hr) || (m_pCurrentListHead == NULL) ); fExceedCrashLimit = FExceedCrashLimit(); // // Set Event, so that let all other wait threads go // SetEvent(m_hPermitOOPEvent); // // Log the crash. // const CHAR *pszEventLog[1]; pszEventLog[0] = m_strApplicationPath.QueryStr(); // Event log g_pInetSvc->LogEvent(W3_EVENT_OOPAPP_VANISHED, 1, pszEventLog, 0); // // Crash control. If the OOP Server keeps crashing, it's a waste of computer resource // to rebuild a ill-formed ISAPI DLL. Therefore, we will stop to service this WAMINFO // and log our decision in the eventlog. if (fExceedCrashLimit) { // Event log g_pInetSvc->LogEvent(W3_EVENT_NO_MORE_CRASH, 1, pszEventLog, 0); } LExit: return hr; } /*----------------------------------------------------------------------------- CWamInfoOutProc::Cleanup Clean up all wamrequests in a OOPWamRequestList. call WamRequest Self Destruction fuction directly. Argument: pCleanupList: a pointer to COOPWamRequestList. -----------------------------------------------------------------------------*/ BOOL CWamInfoOutProc::Cleanup ( COOPWamReqList* pCleanupList ) { PLIST_ENTRY ple = NULL; WAM_REQUEST* pWamRequest = NULL; BOOL fFoundEntry = TRUE; // Always try to clean up any requests in the list. while (fFoundEntry) { LockList(); if (!IsListEmpty(&(pCleanupList->m_leOOPWamReqListHead))) { ple = RemoveHeadList(&(pCleanupList->m_leOOPWamReqListHead)); DBG_ASSERT(ple); pWamRequest = CONTAINING_RECORD( ple, WAM_REQUEST, m_leOOP); // Completely disassociate the wamreq from this list before // we leave this critical section. InitializeListHead( &pWamRequest->m_leOOP ); if (0 == pWamRequest->InterlockedNonZeroAddRef()) { // Last reference to wamreq is gone, so cleanup is already // happening UnLockList(); continue; } } else { fFoundEntry = FALSE; } UnLockList(); if (fFoundEntry) { HRESULT hr; if ( !pWamRequest->IsChild() ) { pWamRequest->WriteLogInfo( SZ_FAILED_OOP_REQUEST_LOG_MESSAGE, HT_SERVER_ERROR, E_FAIL); } // // Assume all the requests in the stage is only refed by COM. hr = CoDisconnectObject( static_cast(pWamRequest), NULL ); pWamRequest->Release(); } } // Got to assert pCleanupList is cleaned. DBG_ASSERT(IsListEmpty(&(pCleanupList->m_leOOPWamReqListHead))); return TRUE; } /*----------------------------------------------------------------------------- CWamInfoOutProc::CleanupAll This function goes through the CleanupLists. It skips the current OOP list during the walk through, If an CleanupList's timestamp indicates it is ready to cleanup, it triggers a cleanup of WAMREQUESTs in that list. After the cleanup, the CleanupList will be removed from the linklist of CleanupList. If a Cleanuplist contains no WAMREQUEST, this function will remove the CleanupList from the linklist of CleanupList. Parameter: fScheduled: TRUE if the function is called in a scheduler. FALSE, otherwise. return: BOOL Note: This function does not touch the CleanupList pointed by m_pCurrentListHead. -----------------------------------------------------------------------------*/ BOOL CWamInfoOutProc::CleanupAll ( BOOL fScheduled ) { BOOL fReturn = TRUE; PLIST_ENTRY pTemp = NULL; COOPWamReqList* pCleanupList = NULL; DWORD dwCurrentTime = GetTickCount(); LIST_ENTRY rgCleanupListHead; InitializeListHead(&rgCleanupListHead); DBG_CODE ( DWORD cnt = 0; ); LockList(); pTemp = (&m_rgRecoverListHead)->Flink; while (pTemp != &m_rgRecoverListHead) { DBG_ASSERT(pTemp != NULL); pCleanupList = CONTAINING_RECORD(pTemp, COOPWamReqList, m_leRecoverListLink); pTemp = pTemp->Flink; // // Skip the Current Active list. A rare case when called from a Scheduler, // the m_pCurrentListHead gets changed during the loop, in that case, we just // have more than 2 LinkLists after the loop, therefore, a Cleanup will be scheduled // again. // if (IsListEmpty(&pCleanupList->m_leOOPWamReqListHead) && !pCleanupList->FActive()) { // // pCleanupList record can be released. // If pCleanupList is active, move on to the next Link. // Otherwise, the list needs to be cleaned. // RemoveEntryList(&pCleanupList->m_leRecoverListLink); InterlockedDecrement((LPLONG)&m_cRecoverList); delete pCleanupList; } else if(pCleanupList->FTimeToCleanup(dwCurrentTime)) { // // pCleanupList is timedout, therefore, it is ok to clean all WamRequests // in that list and then remove the list. // // RemoveEntryList(&pCleanupList->m_leRecoverListLink); InterlockedDecrement((LPLONG)&m_cRecoverList); InsertTailList(&rgCleanupListHead, &pCleanupList->m_leRecoverListLink); } else {// No Op. Keep the list there. } } if (fScheduled) { // // There are some cleanup list other than the current active list, // therefore, need to schedule another work item. // if (m_cRecoverList > 1) { m_idScheduled = ScheduleWorkItem ( CWamInfoOutProc::CleanupScheduled, (VOID *)this, DEFAULT_CLEANUP_WAIT, FALSE ); DBG_ASSERT(m_idScheduled != 0); if (m_idScheduled == 0) { fReturn = FALSE; } } else { // // otherwise, no more to cleanup. Since this is called from Scheduled thread, // therefore, m_pCurrentListHead is a valid OOP List head. Then, cleanup list // count == 1 tells us no other cleanup lists need to be cleanup. reset the // scheduled id to be 0. // m_idScheduled = 0; } } else { // // If this is called right after a crash, then, we need to check // if there is not scheduled workitem and there are some wam requests on // the current OOPList, we need to schedule a workitem. // if (m_idScheduled == 0 && !IsListEmpty(m_pCurrentListHead)) { m_idScheduled = ScheduleWorkItem ( CWamInfoOutProc::CleanupScheduled, (VOID *)this, DEFAULT_CLEANUP_WAIT, FALSE ); DBG_ASSERT(m_idScheduled != 0); if (m_idScheduled == 0) { fReturn = FALSE; } } } UnLockList(); // // Cleanup the lists. // while (!IsListEmpty(&rgCleanupListHead)) { pTemp = RemoveHeadList(&rgCleanupListHead); pCleanupList = CONTAINING_RECORD(pTemp, COOPWamReqList, m_leRecoverListLink); Cleanup(pCleanupList); delete pCleanupList; DBG_CODE(cnt++;); } DBG_CODE ( DBGPRINTF((DBG_CONTEXT, "Application %s(%08x) CleanupAll cleaned up %d list(s), idScheduled %d, m_cRecoverList %d\n", m_strApplicationPath.QueryStr(), this, cnt, m_idScheduled, m_cRecoverList)); ); return fReturn; } /*----------------------------------------------------------------------------- CWamInfoOutProc::FinalCleanup Cleanup function called at Shutdown. It cancelled any out-standing scheduled workitem. And do the clean up itself. Argument: None. -----------------------------------------------------------------------------*/ BOOL CWamInfoOutProc::FinalCleanup(VOID) { DWORD cList = m_cRecoverList; // Count of CleanupList in the WamInfo. COOPWamReqList* pList = NULL; PLIST_ENTRY ple = NULL; if (0 != m_idScheduled) { // // Still have out-standing Scheduled cleanup work item. // Remove the scheduled cleanup work item here. // And let me clean up. // DWORD dwOldCookie = m_idScheduled; BOOL fRemoved; fRemoved = RemoveWorkItem(dwOldCookie); if (fRemoved) { // The Work Item is removed. // This thread is going to clean up. InterlockedExchange((LPLONG)&(m_idScheduled), 0); } else { DWORD dwErr = GetLastError(); DBGPRINTF((DBG_CONTEXT, "Application %s(%08x) failed to removeWorkItem(%d), ErrCode %d\n", m_strApplicationPath.QueryStr(), this, m_idScheduled, dwErr)); DBG_ASSERT(FALSE); } } // // Walk through the cleanuplist head list, remove all cleanup list. // while(!IsListEmpty(&m_rgRecoverListHead)) { LockList(); ple = RemoveHeadList(&m_rgRecoverListHead); UnLockList(); pList = CONTAINING_RECORD(ple, COOPWamReqList, m_leRecoverListLink); DBG_ASSERT(pList); // No out-standing Scheduled cleanup work item. // Verify the list should be clean. // if ( !(IsListEmpty(&(pList->m_leOOPWamReqListHead)))) { // The List can have some element without a WorkItem Scheduled. // A crash happens right after a request finishs OOP operation but before // COM releases the request's refs. Cleanup(pList); } // // Work Item is already finished. // The List should be empty now. // DBG_ASSERT(IsListEmpty(&(pList->m_leOOPWamReqListHead))); delete pList; pList = NULL; cList--; } // // cList should be 0 and the ListHead should be empty too. // DBG_ASSERT(cList == 0 && IsListEmpty(&m_rgRecoverListHead)); m_cRecoverList = cList; m_pCurrentListHead = NULL; return TRUE; } /*----------------------------------------------------------------------------- CWamInfoOutProc::CleanupScheduled Call back function for Cleanup. Argument: pContext: a pointer to COOPWamReqList.(refer to header file for the definition of COOPWamReqList). -----------------------------------------------------------------------------*/ VOID CWamInfoOutProc::CleanupScheduled ( VOID *pContext ) { CWamInfoOutProc* pWamInfoOutProc = reinterpret_cast(pContext); DBG_ASSERT(pWamInfoOutProc); // // Call CleanupAll // pWamInfoOutProc->CleanupAll(TRUE); return; } /*----------------------------------------------------------------------------- CWamInfoOutProc::ReInit Reinit a Wam. Argument: NONE. -----------------------------------------------------------------------------*/ HRESULT CWamInfoOutProc::ReInitWam ( VOID ) { HRESULT hr = NOERROR; WCHAR wszPackageId[uSizeCLSIDStr]; ChangeToState(WIS_REPAIR); DBG_ASSERT(m_pProcessEntry); memcpy(wszPackageId, m_pProcessEntry->QueryPackageId(), sizeof(wszPackageId)); if (m_pIWam != NULL) { IWam * pIWam; hr = g_GIPAPI.Get(m_dwIWamGipCookie, IID_IWam, (void **) &pIWam); if(SUCCEEDED(hr)) { pIWam->UninitWam(); pIWam->Release(); } hr = g_GIPAPI.Revoke(m_dwIWamGipCookie); // balance the AddRef in CWamInfo::Init. m_pIWam->Release(); m_pIWam = NULL; } // If we got a handle for an Out Of Proc Wam, release it if( m_pProcessEntry != NULL) { g_pWamDictator->m_PTable.RemoveWamInfoFromProcessTable(this); m_pProcessEntry = NULL; } // // This Dereference() balanced the AddRef in the old WAM Init time. // DBGPRINTF(( DBG_CONTEXT , "Dereference CWamInfoOutProc(%08x)\n" , this )); Dereference(); // Ok. We make a new Wam. hr = CWamInfo::Init((WCHAR*)wszPackageId, g_pWamDictator->QueryInetInfoPid()); if (SUCCEEDED(hr)) { DBGPRINTF((DBG_CONTEXT, "ReInited a new Wam. \n")); if (m_fJobEnabled) { DBG_ASSERT(m_pwsiInstance != NULL); m_pwsiInstance->AddProcessToJob(m_pProcessEntry->QueryProcessHandle(), TRUE); } if (SUCCEEDED(hr)) { ChangeToState(WIS_RUNNING); } } // if shtudown process has been started, changed state to STOP_PENDING. if (m_fShuttingDown) { ChangeToState(WIS_SHUTDOWN); } return hr; } VOID CWamInfoOutProc::NotifyGetInfoForName ( IN LPCSTR pszServerVariable ) { DBG_ASSERT( pszServerVariable ); if( pszServerVariable && *pszServerVariable != '\0' ) { const SV_CACHE_MAP & refSVMap = g_pWamDictator->QueryServerVariableMap(); DWORD dwOrdinal; if( refSVMap.FindOrdinal( pszServerVariable, // Could get len on hash strlen( pszServerVariable ), &dwOrdinal ) ) { m_svCache.SetCacheIt( dwOrdinal ); } } } HRESULT CWamInfoOutProc::DoProcessRequestCall ( IN IWam * pIWam, IN WAM_REQUEST * pWamRequest, OUT BOOL * pfHandled ) { DBG_ASSERT(pfHandled); DBG_ASSERT(pWamRequest); DBG_ASSERT(pIWam); // // Using _alloca to get the core state data is "probably" // perfectly safe here. Since this call is heading oop the stack // is going to bottom out fairly soon. // // But the core state, in the case of a post or put could be quite // large. and there is currently no bound on the amount of data that // could be part of the server variable cache. So we'll use // dwMaxStackAlloc as a threshold for using alloca // const DWORD dwMaxStackAlloc = (1024 * 4) - sizeof(DWORD); BOOL fHeapAllocCore = FALSE; BOOL fHeapAllocServerVars = FALSE; HRESULT hr = NOERROR; DWORD cbWrcStrings; DWORD cbServerVars; OOP_CORE_STATE oopCoreState; ZeroMemory( &oopCoreState, sizeof(OOP_CORE_STATE) ); // Copy the current cache list SV_CACHE_LIST svCache( m_svCache ); SV_CACHE_LIST::BUFFER_ITEM svCacheItems[SVID_COUNT]; DWORD cCachedServerVariables = SVID_COUNT; svCache.GetBufferItems( svCacheItems, &cCachedServerVariables ); // Get the core state WAM_REQ_CORE_FIXED wrc; oopCoreState.cbFixedCore = sizeof( WAM_REQ_CORE_FIXED ); oopCoreState.pbFixedCore = (LPBYTE)&wrc; cbWrcStrings = pWamRequest->CbWrcStrings( m_fInProcess ); oopCoreState.cbCoreState = WRC_CB_FIXED_ARRAYS + cbWrcStrings; if( oopCoreState.cbCoreState > dwMaxStackAlloc ) { fHeapAllocCore = TRUE; oopCoreState.pbCoreState = (LPBYTE)LocalAlloc( LPTR, oopCoreState.cbCoreState ); if( oopCoreState.pbCoreState == NULL ) { hr = E_OUTOFMEMORY; } } else { oopCoreState.pbCoreState = (LPBYTE)_alloca( oopCoreState.cbCoreState ); } if( SUCCEEDED(hr) ) { DBG_ASSERT( oopCoreState.pbCoreState ); // // Load the core state. This will fail if the oop application // has died. In this case, the easiest thing to do is to make // the call to IWam::Process request with a NULL core state // and let the failure get handled back by the normal code path // hr = pWamRequest->GetCoreState( oopCoreState.cbCoreState, oopCoreState.pbCoreState, oopCoreState.cbFixedCore, oopCoreState.pbFixedCore ); } // Get the server variable cache data if( SUCCEEDED(hr) ) { // Temporarily use a separate buffer for the server variable values cbServerVars = pWamRequest->CbCachedSVStrings( svCacheItems, cCachedServerVariables ); if( cbServerVars ) { DBG_ASSERT( cCachedServerVariables > 0 ); // Temporarily use a separate buffer oopCoreState.cbServerVarData = cbServerVars; if( cbServerVars > dwMaxStackAlloc ) { fHeapAllocServerVars = TRUE; oopCoreState.pbServerVarData = (LPBYTE)LocalAlloc( LPTR, cbServerVars ); if( oopCoreState.pbServerVarData == NULL ) { hr = E_OUTOFMEMORY; } } else { oopCoreState.pbServerVarData = (LPBYTE)_alloca( cbServerVars ); } if( SUCCEEDED(hr) ) { DBG_ASSERT( oopCoreState.pbServerVarData ); hr = pWamRequest->GetCachedSVStrings( oopCoreState.pbServerVarData, oopCoreState.cbServerVarData, svCacheItems, cCachedServerVariables ); if( SUCCEEDED(hr) ) { oopCoreState.pbServerVarCache = (LPBYTE)svCacheItems; oopCoreState.cbServerVars = cCachedServerVariables * sizeof(SV_CACHE_LIST::BUFFER_ITEM); } } } } OOP_CORE_STATE * pOopCoreState = (SUCCEEDED(hr)) ? &oopCoreState : NULL; // The last thing we do... hr = pIWam->ProcessRequest( pWamRequest, cbWrcStrings, pOopCoreState, pfHandled ); if( fHeapAllocCore && oopCoreState.pbCoreState ) { LocalFree( oopCoreState.pbCoreState ); } if( fHeapAllocServerVars && oopCoreState.pbServerVarData ) { LocalFree( oopCoreState.pbServerVarData ); } return hr; } HRESULT CWamInfoOutProc::GetStatistics ( DWORD Level, LPWAM_STATISTICS_INFO pWamStatsInfo ) { HRESULT hr = S_OK; IWam* pIWam = NULL; DWORD dwVersion; if (!FExceedCrashLimit()) { pIWam = m_pIWam; } if (NULL == pIWam) { // We reach this state only if we exceed the crash limit. DBG_ASSERT(FExceedCrashLimit()); hr = HRESULT_FROM_WIN32(RPC_S_CALL_FAILED_DNE); } else { hr = pIWam->GetStatistics(0, pWamStatsInfo); // 1> If the OOP call failed during the call, RPC returns RPC_S_CALL_FAILED. // ie. OOP process crashed during the call // 2> If OOP process crashed before the call, RPC returns RPC_S_CALL_FAILED_DNE. // // Subsequest requests to the crashed OOP Process will get RPC_S_SERVER_UNAVAILABLE. // if (RPC_S_CALL_FAILED == WIN32_FROM_HRESULT(hr) || RPC_S_CALL_FAILED_DNE == WIN32_FROM_HRESULT(hr) || RPC_S_SERVER_UNAVAILABLE == WIN32_FROM_HRESULT(hr)) { const BOOL F_INRECOVERY = TRUE; const BOOL F_HEALTHY = FALSE; if (F_INRECOVERY == (BOOL)g_pfnInterlockedCompareExchange( (LONG *)&m_fInRepair, (LONG)F_INRECOVERY, (LONG)F_HEALTHY)) { // Other thread already doing the recovery job. DBGPRINTF((DBG_CONTEXT, "Recovery mode: Other thread is doing recovery job.\n")); hr = NOERROR; } // // Try to Repair(call repair function). But other thread might already working on the repairing // Therefore, this thread will return immediately if repairing is in-place. // Repair(); if (F_HEALTHY == (BOOL)g_pfnInterlockedCompareExchange( (LONG *)&m_fInRepair, (LONG)F_HEALTHY, (LONG)F_INRECOVERY)) { // Other thread already doing the recovery job. DBG_ASSERT(FALSE); } } } return hr; } /************************ End of File ***********************/