/*=================================================================== Microsoft Denali Microsoft Confidential. Copyright 1997 Microsoft Corporation. All Rights Reserved. Component: Viper Integration Objects File: viperint.cpp Owner: DmitryR This file contains the implementation of viper integration classes ===================================================================*/ #include "denpre.h" #pragma hdrstop #include "Context.h" #include "package.h" #include "memchk.h" #include "denali.h" #include "perfdata.h" #include "etwtrace.hxx" extern HDESK ghDesktop; // // COM holds the last reference to a CViperAsyncRequest // we need to track these objects to ensure that we don't // exit before the activity threads have released them. // volatile LONG g_nViperRequests = 0; LONG g_nThreadsExecuting = 0; CViperReqManager g_ViperReqMgr; DWORD g_ReqDisconnected = 0; BOOL g_fFirstMTAHit = TRUE; BOOL g_fFirstSTAHit = TRUE; extern CRITICAL_SECTION g_csFirstMTAHitLock; extern CRITICAL_SECTION g_csFirstSTAHitLock; #if REFTRACE_VIPER_REQUESTS PTRACE_LOG CViperAsyncRequest::gm_pTraceLog=NULL; #endif /*=================================================================== C V i p e r A s y n c R e q u e s t ===================================================================*/ /*=================================================================== CViperAsyncRequest::CViperAsyncRequest CViperAsyncRequest constructor Parameters: Returns: ===================================================================*/ CViperAsyncRequest::CViperAsyncRequest() : m_cRefs(1), m_pHitObj(NULL), m_hrOnError(S_OK), m_pActivity(NULL), m_fTestingConnection(0), m_dwRepostAttempts(0), m_fAsyncCallPosted(0) { #if DEBUG_REQ_SCAVENGER DBGPRINTF((DBG_CONTEXT, "CViperAsyncRequest::CViperAsyncRequest (%p)\n", this)); #endif #if REFTRACE_VIPER_REQUESTS WriteRefTraceLog(gm_pTraceLog, m_cRefs, this); #endif InterlockedIncrement( (LONG *)&g_nViperRequests ); } /*=================================================================== CViperAsyncRequest::~CViperAsyncRequest CViperAsyncRequest destructor Parameters: Returns: ===================================================================*/ CViperAsyncRequest::~CViperAsyncRequest() { #if DEBUG_REQ_SCAVENGER DBGPRINTF((DBG_CONTEXT, "CViperAsyncRequest::~CViperAsyncRequest (%p)\n", this)); #endif InterlockedDecrement( (LONG *)&g_nViperRequests ); } /*=================================================================== CViperAsyncRequest::Init Initialize CViperAsyncRequest with CHitObj object Parameters: CHitObj *pHitObj Denali HitObj Returns: HRESULT ===================================================================*/ HRESULT CViperAsyncRequest::Init ( CHitObj *pHitObj, IServiceActivity *pActivity ) { Assert(m_pHitObj == NULL); m_pHitObj = pHitObj; m_pActivity = pActivity; m_fBrowserRequest = pHitObj->FIsBrowserRequest(); m_dwLastTestTimeStamp = GetTickCount(); // establish the timeout value for this request. It will be a factor // of the configured QueueConnectionTestTime. In proc, it will be // this value times 2, and out of proc it will be multiplied by 4. // NOTE if the QueueConnectionTestTime is zero, we'll use hard // constants 12 and 6 for oop and inproc, respectively. DWORD dwQueueConnectionTestTime = pHitObj->PAppln()->QueryAppConfig()->dwQueueConnectionTestTime(); m_dwTimeout = dwQueueConnectionTestTime ? dwQueueConnectionTestTime * (g_fOOP ? 4 : 2) : g_fOOP ? 12 : 6; return S_OK; } #ifdef DBG /*=================================================================== CViperAsyncRequest::AssertValid Test to make sure that this is currently correctly formed and assert if it is not. Returns: ===================================================================*/ void CViperAsyncRequest::AssertValid() const { Assert(m_pHitObj); Assert(m_cRefs > 0); } #endif /*=================================================================== CViperAsyncRequest::QueryInterface Standard IUnknown method Parameters: REFIID iid void **ppv Returns: HRESULT ===================================================================*/ STDMETHODIMP CViperAsyncRequest::QueryInterface ( REFIID iid, void **ppv ) { if (iid == IID_IUnknown || iid == IID_IServiceCall) { *ppv = this; AddRef(); return S_OK; } else if (iid == IID_IAsyncErrorNotify) { *ppv = static_cast(this); AddRef(); return S_OK; } return E_NOINTERFACE; } /*=================================================================== CViperAsyncRequest::AddRef Standard IUnknown method Parameters: Returns: Ref count ===================================================================*/ STDMETHODIMP_(ULONG) CViperAsyncRequest::AddRef() { LONG refs = InterlockedIncrement(&m_cRefs); #if REFTRACE_VIPER_REQUESTS WriteRefTraceLog(gm_pTraceLog, refs, this); #endif return refs; } /*=================================================================== CViperAsyncRequest::Release Standard IUnknown method Parameters: Returns: Ref count ===================================================================*/ STDMETHODIMP_(ULONG) CViperAsyncRequest::Release() { LONG refs = InterlockedDecrement(&m_cRefs); #if REFTRACE_VIPER_REQUESTS WriteRefTraceLog(gm_pTraceLog, refs, this); #endif if (refs != 0) return refs; delete this; return 0; } /*=================================================================== CViperAsyncRequest::OnCall IMTSCall method implementation. This method is called by Viper from the right thread when it's time to process a request Parameters: Returns: HRESULT ===================================================================*/ STDMETHODIMP CViperAsyncRequest::OnCall() { Assert(m_pHitObj); CIsapiReqInfo *pIReq; HCONN ConnId = 0; InterlockedIncrement(&g_nThreadsExecuting); // check to see if the request has been removed from the queue // already. If so, get out of here quick. if (g_ViperReqMgr.RemoveReqObj(this) == FALSE) { Release(); InterlockedDecrement(&g_nThreadsExecuting); return S_OK; } pIReq = m_pHitObj->PIReq(); BOOL fRequestReposted = FALSE; // add an extra addref here to prevent the deletion of the // hitobj deleting the CIsapiReqInfo for this request. if (pIReq) { pIReq->AddRef(); // // Add an ETW trace event when the request is getting processed. // if (ETW_IS_TRACE_ON(ETW_LEVEL_CP) && g_pEtwTracer ) { ConnId = pIReq->ECB()->ConnID; g_pEtwTracer->EtwTraceEvent(&AspEventGuid, EVENT_TRACE_TYPE_START, &ConnId, sizeof(HCONN), NULL, 0); } } m_pHitObj->ViperAsyncCallback(&fRequestReposted); // Make sure there always is DONE_WITH_SESSION if (m_pHitObj->FIsBrowserRequest() && !fRequestReposted) { if (!m_pHitObj->FDoneWithSession()) m_pHitObj->ReportServerError(IDE_UNEXPECTED); } if (!fRequestReposted) delete m_pHitObj; // don't delete if reposted m_pHitObj = NULL; // set to NULL even if not deleted Release(); // release this, Viper holds another ref // // Add an ETW event when we are done with the request // if (pIReq) { if ( ETW_IS_TRACE_ON(ETW_LEVEL_CP) && g_pEtwTracer) { g_pEtwTracer->EtwTraceEvent(&AspEventGuid, ETW_TYPE_END, &ConnId, sizeof(HCONN), NULL, 0); } pIReq->Release(); } InterlockedDecrement(&g_nThreadsExecuting); return S_OK; } /*=================================================================== CViperAsyncRequest::OnError Called by COM+ when it is unable to dispatch the request properly on the configured thread Parameters: Returns: HRESULT ===================================================================*/ STDMETHODIMP CViperAsyncRequest::OnError(HRESULT hrViperError) { Assert(m_pHitObj); CIsapiReqInfo *pIReq; HRESULT hr = S_OK; // check to see if the request has been removed from the queue // already. If so, get out of here quick. if (g_ViperReqMgr.RemoveReqObj(this) == FALSE) { Release(); return S_OK; } pIReq = m_pHitObj->PIReq(); if (pIReq) pIReq->AddRef(); m_dwRepostAttempts++; // attempt to repost the request if it hasn't errored out three // times yet. if (m_dwRepostAttempts <= 3) { hr = m_pActivity->AsynchronousCall(this); Assert(SUCCEEDED(hr)); } // if it has errored out three times or the repost failed, // pitch the request if (FAILED(hr) || (m_dwRepostAttempts > 3)) { // DONE_WITH_SESSION -- ServerSupportFunction // does not need bracketing if (m_pHitObj->FIsBrowserRequest()) m_pHitObj->ReportServerError(IDE_UNEXPECTED); // We never called to process request, there should // be no state and it's probably save to delete it // outside of bracketing delete m_pHitObj; m_pHitObj = NULL; // set to NULL even if not deleted Release(); // release this, Viper holds another ref } if (pIReq) pIReq->Release(); return S_OK; } DWORD CViperAsyncRequest::SecsSinceLastTest() { DWORD dwt = GetTickCount(); if (dwt >= m_dwLastTestTimeStamp) return ((dwt - m_dwLastTestTimeStamp)/1000); else return (((0xffffffff - m_dwLastTestTimeStamp) + dwt)/1000); } /*=================================================================== C V i p e r A c t i v i t y ===================================================================*/ /*=================================================================== CViperActivity::CViperActivity CViperActivity constructor Parameters: Returns: ===================================================================*/ CViperActivity::CViperActivity() : m_pActivity(NULL), m_cBind(0) { } /*=================================================================== CViperActivity::~CViperActivity CViperActivity destructor Parameters: Returns: ===================================================================*/ CViperActivity::~CViperActivity() { UnInit(); } /*=================================================================== CViperActivity::Init Create actual Viper activity using MTSCreateActivity() Parameters: Returns: HRESULT ===================================================================*/ HRESULT CViperActivity::Init(IUnknown *pServicesConfig) { Assert(!FInited()); HRESULT hr = S_OK; hr = CoCreateActivity(pServicesConfig, IID_IServiceActivity, (void **)&m_pActivity); if (FAILED(hr)) return hr; m_cBind = 1; return S_OK; } /*=================================================================== CViperActivity::InitClone Clone Viper activity (AddRef() it) Parameters: CViperActivity *pActivity activity to clone from Returns: HRESULT ===================================================================*/ HRESULT CViperActivity::InitClone ( CViperActivity *pActivity ) { Assert(!FInited()); Assert(pActivity); pActivity->AssertValid(); m_pActivity = pActivity->m_pActivity; m_pActivity->AddRef(); m_cBind = 1; return S_OK; } /*=================================================================== CViperActivity::UnInit Release Viper activity Parameters: Returns: HRESULT ===================================================================*/ HRESULT CViperActivity::UnInit() { if (m_pActivity) { while (m_cBind > 1) // 1 is for inited flag { m_pActivity->UnbindFromThread(); m_cBind--; } m_pActivity->Release(); m_pActivity = NULL; } m_cBind = 0; return S_OK; } /*=================================================================== CViperActivity::BindToThread Bind Activity to current thread using IMTSActivity method Parameters: Returns: HRESULT ===================================================================*/ HRESULT CViperActivity::BindToThread() { Assert(FInited()); m_pActivity->BindToCurrentThread(); m_cBind++; return S_OK; } /*=================================================================== CViperActivity::UnBindFromThread UnBind Activity from using IMTSActivity method Parameters: Returns: HRESULT ===================================================================*/ HRESULT CViperActivity::UnBindFromThread() { Assert(FInited()); Assert(m_cBind > 1); m_pActivity->UnbindFromThread(); m_cBind--; return S_OK; } /*=================================================================== CViperActivity::PostAsyncRequest Call HitObj Async. Creates IMTSCCall object to do it. Parameters: CHitObj *pHitObj Denali's HitObj Returns: HRESULT ===================================================================*/ HRESULT CViperActivity::PostAsyncRequest ( CHitObj *pHitObj ) { AssertValid(); HRESULT hr = S_OK; CViperAsyncRequest *pViperCall = new CViperAsyncRequest; if (!pViperCall) hr = E_OUTOFMEMORY; else hr = pViperCall->Init(pHitObj, m_pActivity); // Revert to Self before exiting so that it will be in a consistent (impersonated) state // when it leaves this function. RevertToSelf(); // hr here can be either S_OK or E_OUTOFMEMORY both cases are handled in the last IF. // if (FAILED(hr) && pViperCall). However, if Init ever returns an HR other than S_OK we // will need to place a return right here. if (FAILED(hr)) { if (pViperCall) pViperCall->Release(); return hr; } // // Make the locking of the light weight queue and the subsequent queuing to COM atomic // g_ViperReqMgr.LockQueue(); hr = g_ViperReqMgr.AddReqObj(pViperCall); // // Assume that the ViperAsyncCall will succeed and set it the POSTed flag, // if it fails then cleanup. This avoids a race condition where the object is touched after we // have posted to request to Viper and it has deleted the object. // if (SUCCEEDED(hr)) { pViperCall->SetAsyncCallPosted(); hr = m_pActivity->AsynchronousCall(pViperCall); } // // We can release the lock as the request has now been queued to COM // g_ViperReqMgr.UnlockQueue(); // // If we added it to the queue and the async call failed we have to be sure // that the watch thread did not remove the object off the light weight queue. // If RemoveReqObj returns false the watch thread has done the needful. // if (FAILED(hr) && g_ViperReqMgr.RemoveReqObj(pViperCall)) { // cleanup if AsyncCall failed pViperCall->ClearAsyncCallPosted(); pViperCall->Release(); } return hr; } /*=================================================================== CViperActivity::PostGlobalAsyncRequest Static method. Post async request without an activity. Creates temporary activity Parameters: CHitObj *pHitObj Denali's HitObj Returns: HRESULT ===================================================================*/ HRESULT CViperActivity::PostGlobalAsyncRequest ( CHitObj *pHitObj ) { HRESULT hr = S_OK; CViperActivity *pTmpActivity = new CViperActivity; if (!pTmpActivity) hr = E_OUTOFMEMORY; if (SUCCEEDED(hr)) hr = pTmpActivity->Init(pHitObj->PAppln()->PServicesConfig()); if (SUCCEEDED(hr)) { // remember this activity as HitObj's activity // HitObj will get rid of it on its destructor pHitObj->SetActivity(pTmpActivity); hr = pTmpActivity->PostAsyncRequest(pHitObj); pTmpActivity = NULL; // don't delete, HitObj will } if (pTmpActivity) delete pTmpActivity; return hr; } #ifdef DBG /*=================================================================== CViperAsyncRequest::AssertValid Test to make sure that this is currently correctly formed and assert if it is not. Returns: ===================================================================*/ void CViperActivity::AssertValid() const { Assert(FInited()); Assert(m_pActivity); } #endif #ifdef UNUSED /*=================================================================== C V i p e r T h r e a d E v e n t s ===================================================================*/ /*=================================================================== CViperThreadEvents::CViperThreadEvents CViperThreadEvents constructor Parameters: Returns: ===================================================================*/ CViperThreadEvents::CViperThreadEvents() : m_cRefs(1) { } #ifdef DBG /*=================================================================== CViperThreadEvents::AssertValid Test to make sure that this is currently correctly formed and assert if it is not. Returns: ===================================================================*/ void CViperThreadEvents::AssertValid() const { Assert(m_cRefs > 0); Assert(ghDesktop != NULL); } #endif /*=================================================================== CViperThreadEvents::QueryInterface Standard IUnknown method Parameters: REFIID iid void **ppv Returns: HRESULT ===================================================================*/ STDMETHODIMP CViperThreadEvents::QueryInterface ( REFIID iid, void **ppv ) { if (iid == IID_IUnknown || iid == IID_IThreadEvents) { *ppv = this; AddRef(); return S_OK; } return E_NOINTERFACE; } /*=================================================================== CViperThreadEvents::AddRef Standard IUnknown method Parameters: Returns: Ref count ===================================================================*/ STDMETHODIMP_(ULONG) CViperThreadEvents::AddRef() { DWORD cRefs = InterlockedIncrement((LPLONG)&m_cRefs); return cRefs; } /*=================================================================== CViperThreadEvents::Release Standard IUnknown method Parameters: Returns: Ref count ===================================================================*/ STDMETHODIMP_(ULONG) CViperThreadEvents::Release() { DWORD cRefs = InterlockedDecrement((LPLONG)&m_cRefs); if (cRefs) return cRefs; delete this; return 0; } /*=================================================================== CViperThreadEvents::OnStartup IThreadEvents method implementation. This method is called by Viper whenever they start up a thread. Parameters: None Returns: HRESULT ===================================================================*/ STDMETHODIMP CViperThreadEvents::OnStartup() { HRESULT hr; AssertValid(); // Set the desktop for this thread hr = SetDesktop(); return hr; } /*=================================================================== CViperThreadEvents::OnShutdown IThreadEvents method implementation. This method is called by Viper whenever they shut down a thread. Parameters: None Returns: HRESULT ===================================================================*/ STDMETHODIMP CViperThreadEvents::OnShutdown() { AssertValid(); return S_OK; } #endif //UNUSED /*=================================================================== C V i p e r A c t i v i t y ===================================================================*/ /*=================================================================== CViperReqManager::CViperReqManager Parameters: Returns: VOID ===================================================================*/ CViperReqManager::CViperReqManager() { m_dwReqObjs = 0; m_fCsInited = FALSE; m_fCsQueueInited = FALSE; m_fShutdown = FALSE; m_fDisabled = FALSE; m_hThreadAlive = NULL; m_hWakeUpEvent = NULL; } /*=================================================================== CViperReqManager::Init Parameters: Returns: HRESULT ===================================================================*/ HRESULT CViperReqManager::Init() { HRESULT hr = S_OK; DWORD dwRegValue; if (m_fDisabled == TRUE) return S_OK; m_dwQueueMin = Glob(dwRequestQueueMax) / (g_fOOP ? 5 : 10); m_dwLastAwakened = GetTickCount()/1000; m_dwQueueAlwaysWakeupMin = (Glob(dwRequestQueueMax)*80)/100; #if REFTRACE_VIPER_REQUESTS CViperAsyncRequest::gm_pTraceLog = CreateRefTraceLog(10000, 0); #endif ErrInitCriticalSection(&m_csLock, hr); if (SUCCEEDED(hr)) { m_fCsInited = TRUE; ErrInitCriticalSection(&m_csQueueLock, hr); if (SUCCEEDED(hr)) { m_fCsQueueInited = TRUE; m_hWakeUpEvent = IIS_CREATE_EVENT("CViperReqManager::m_hWakeUpEvent", this, TRUE, // flag for manual-reset event FALSE); // flag for initial state if (!m_hWakeUpEvent) hr = E_OUTOFMEMORY; } } if (SUCCEEDED(hr)) { m_hThreadAlive = CreateThread(NULL, 0, CViperReqManager::WatchThread, 0, 0, NULL); if (!m_hThreadAlive) { hr = HRESULT_FROM_WIN32(GetLastError()); } } if (FAILED(hr) && m_hWakeUpEvent) CloseHandle(m_hWakeUpEvent); if (SUCCEEDED(g_AspRegistryParams.GetF5AttackValue(&dwRegValue))) m_fDisabled = !dwRegValue; return hr; } /*=================================================================== CViperReqManager::UnInit Parameters: Returns: HRESULT ===================================================================*/ HRESULT CViperReqManager::UnInit() { HRESULT hr = S_OK; if (m_fDisabled == TRUE) return S_OK; // mark that we're in shutdown m_fShutdown = TRUE; #if REFTRACE_VIPER_REQUESTS DestroyRefTraceLog(CViperAsyncRequest::gm_pTraceLog); #endif // if the watch thread is still alive, wake it up and wait for // it to die off if (m_hThreadAlive) { WakeUp(TRUE); if (WaitForSingleObject (m_hThreadAlive, INFINITE) == WAIT_FAILED) { hr = HRESULT_FROM_WIN32(GetLastError()); } CloseHandle (m_hThreadAlive); m_hThreadAlive = NULL; } if (m_fCsInited) { DeleteCriticalSection(&m_csLock); } if (m_fCsQueueInited) { DeleteCriticalSection(&m_csQueueLock); } if (m_hWakeUpEvent) CloseHandle(m_hWakeUpEvent); m_hWakeUpEvent = NULL; return hr; } /*=================================================================== CViperReqManager::WatchThread Parameters: Returns: DWORD This thread is awakened to search for queued requests that should be tested to see if they still have connected clients on the other end. ::GetNext() is the key routine called to access a queued request. ::GetNext() will determine if there are requests that should be tested and return only those. ===================================================================*/ DWORD __stdcall CViperReqManager::WatchThread(VOID *pArg) { DWORD dwReqsToTest = Glob(dwRequestQueueMax)/10; DWORD dwF5AttackThreshold = (Glob(dwRequestQueueMax)*90)/100; // live in a do-while loop do { CViperAsyncRequest *pViperReq = NULL; CViperAsyncRequest *pNextViperReq = NULL; BOOL fTestForF5Attack = FALSE; DWORD dwReqsDiscToCont = 0; DWORD dwReqsDisconnected = 0; DWORD dwReqsTested = 0; // wait for single object ResetEvent(g_ViperReqMgr.m_hWakeUpEvent); // check for shutdown if (g_ViperReqMgr.m_fShutdown) goto LExit; WaitForSingleObject(g_ViperReqMgr.m_hWakeUpEvent, INFINITE); g_ViperReqMgr.m_dwLastAwakened = GetTickCount()/1000; // check for shutdown if (g_ViperReqMgr.m_fShutdown) goto LExit; // determine the type of cleanup we're going to perform if (g_ViperReqMgr.m_dwReqObjs >= dwF5AttackThreshold) { // testing for a possible F5Attack. This will cause this loop // to test the most recently received requests to check for a large // number of disconnected requests. We'll test each 10% of the queue // to see if at least 75% of the requests are disconnected. If 75% or // more are disconnected, then we'll test the next 10%. If less than // 75%, then we aren't under an F5Attack and will stop testing for an // F5Attack. #if DEBUG_REQ_SCAVENGER DBGPRINTF((DBG_CONTEXT, "CViperReqManager::WatchThread - testing for F5Attack\r\n")); #endif fTestForF5Attack = TRUE; dwReqsDiscToCont = (dwReqsToTest * 75)/100; dwReqsDisconnected = 0; dwReqsTested = 0; } else { // not testing for F5Attack. We will test all requests that have // been on the queue for the minimum amount of time - 5 seconds if // inproc, else 10 seconds. #if DEBUG_REQ_SCAVENGER DBGPRINTF((DBG_CONTEXT, "CViperReqManager::WatchThread - NOT testing for F5Attack\r\n")); #endif fTestForF5Attack = FALSE; } // get the first request that is a candidate for testing pNextViperReq = g_ViperReqMgr.GetNext(NULL, fTestForF5Attack); // process all the available while (pViperReq = pNextViperReq) { // AddRef here to make sure the ViperReq object doesn't go away // on the STA thread underneath of us. pViperReq->AddRef(); // check again for shutdown. GetNext() sets the testing // connection flag if (g_ViperReqMgr.m_fShutdown) { pViperReq->ClearTestingConnection(); pViperReq->Release(); goto LExit; } // if testing for F5Attack, check to see if we've tested the first // 10% of the requests on the list. If so, see if we've found that // 75% of them were disconnected. If not, then break. We're not under // attack. Otherwise, continue testing. if (fTestForF5Attack && (dwReqsTested == dwReqsToTest)) { if (dwReqsDisconnected < dwReqsDiscToCont) { #if DEBUG_REQ_SCAVENGER DBGPRINTF((DBG_CONTEXT, "CViperReqManager::WatchThread - 75% of requests NOT disconnected. Stopping.\r\n")); #endif // didn't see 75%, bail pViperReq->ClearTestingConnection(); pViperReq->Release(); break; } #if DEBUG_REQ_SCAVENGER DBGPRINTF((DBG_CONTEXT, "CViperReqManager::WatchThread - 75% of requests disconnected. Continuing.\r\n")); #endif // at least 75% were disconnected. Reset running counters // and continue dwReqsDisconnected = 0; dwReqsTested = 0; } dwReqsTested++; BOOL fConnected = TRUE; // // test the connection but do so only after we are sure it has been queued to COM // g_ViperReqMgr.LockQueue(); if (!pViperReq->PHitObj()->PIReq()->TestConnection(&fConnected)) fConnected = TRUE; g_ViperReqMgr.UnlockQueue(); #if DEBUG_REQ_SCAVENGER DBGPRINTF((DBG_CONTEXT, "CViperReqManager::WatchThread. Tested - %S (%s) (%p,%p)\n", pViperReq->PHitObj()->PSzCurrTemplateVirtPath(), fConnected ? "TRUE" : "FALSE", pViperReq, pViperReq->PHitObj())); #endif // get the next request. This will leave two requests locked for // testing. We have to get the next request here because we may // remove it below. pNextViperReq = g_ViperReqMgr.GetNext(pViperReq,fTestForF5Attack); // now check fConnected if (!fConnected) { g_ReqDisconnected++; dwReqsDisconnected++; // not connected. Forceably remove the request from the queue. // Passing TRUE ignores the fTestingConnection test. DBG_REQUIRE(g_ViperReqMgr.RemoveReqObj(pViperReq,TRUE) == TRUE); // do the stuff we do in CHitObj::RejectBrowserRequestWhenNeeded(). pViperReq->PHitObj()->SetFExecuting(FALSE); pViperReq->PHitObj()->ReportServerError(IDE_500_SERVER_ERROR); #ifndef PERF_DISABLE g_PerfData.Incr_REQCOMFAILED(); g_PerfData.Decr_REQCURRENT(); #endif delete pViperReq->PHitObj(); } else { // still connected. Update the test stamp and release the testing // bit pViperReq->UpdateTestTimeStamp(); pViperReq->ClearTestingConnection(); } pViperReq->Release(); } // we'll exit the loop here as well if shutdown becomes true. } while(g_ViperReqMgr.m_fShutdown == FALSE); LExit: // // the thread is going away. // return 0; } /*=================================================================== CViperReqManager::AddReqObj Parameters: CViperAsyncRequest *pReqObj - request to add to queue Returns: HRESULT - always returns S_OK ===================================================================*/ HRESULT CViperReqManager::AddReqObj(CViperAsyncRequest *pReqObj) { HRESULT hr = S_OK; #if DEBUG_REQ_SCAVENGER DWORD curObjs; #endif if (m_fDisabled == TRUE) return S_OK; // don't track non-browser requests if (pReqObj->FBrowserRequest() == FALSE) return S_OK; // lock the queue and add the object Lock(); pReqObj->AppendTo(m_ReqObjList); m_dwReqObjs++; #if DEBUG_REQ_SCAVENGER curObjs = m_dwReqObjs; #endif Unlock(); #if DEBUG_REQ_SCAVENGER DBGPRINTF((DBG_CONTEXT, "CViperReqManager::AddReqObj (%p). Total = %d\r\n", pReqObj, curObjs)); #endif // for now, wake up the watch thread everytime. WakeUp(); return hr; } /*=================================================================== CViperReqManager::RemoveReqObj Parameters: CViperAsyncRequest *pReqObj - request to remove from the queue BOOL fForce - remove regardless of testing state Returns: BOOL - TRUE if req was on list, else FALSE ===================================================================*/ BOOL CViperReqManager::RemoveReqObj(CViperAsyncRequest *pReqObj, BOOL fForce) { BOOL fOnList = FALSE; #if DEBUG_REQ_SCAVENGER DWORD curObjs; #endif if (m_fDisabled == TRUE) return TRUE; // if not a browser request, then this queue isn't tracking it. // It's safe to return TRUE if (pReqObj->FBrowserRequest() == FALSE) return TRUE; // can't remove the object from the queue if it is currently // being tested. The exception being if fForce is passed. This // is passed only by the WatchThread. sleepAgain: while((fForce != TRUE) && pReqObj->FTestingConnection()) Sleep(100); // lock and remove the request Lock(); // check to see if after we got the lock, the request is now // being tested. If so, unlock and return to sleeping. if ((fForce != TRUE) && pReqObj->FTestingConnection()) { Unlock(); goto sleepAgain; } // now see if it's on the queue. If FIsEmpty(), then it's not // on the list. if (pReqObj->FIsEmpty()) { fOnList = FALSE; } else { // if it is, Unlink it. Call ClearTestingConnection to release // any other calls to RemoveReqObj that may be waiting. Of course // they all will see that the item is no longer on the list. fOnList = TRUE; pReqObj->UnLink(); pReqObj->ClearTestingConnection(); m_dwReqObjs--; } #if DEBUG_REQ_SCAVENGER curObjs = m_dwReqObjs; #endif Unlock(); #if DEBUG_REQ_SCAVENGER DBGPRINTF((DBG_CONTEXT, "CViperReqManager::RemoveReqObj (%p). fOnList = %d; Total = %d\r\n", pReqObj, fOnList, curObjs)); #endif // wakeup everytime for now... WakeUp(); return fOnList; } /*=================================================================== CViperReqManager::GetNext Parameters: CViperAsyncRequest *pLastReq - position in queue. Returns: CViperAsyncRequest* - non-NULL if request to test, else end of list ===================================================================*/ CViperAsyncRequest *CViperReqManager::GetNext(CDblLink *pLastReq, BOOL fTestForF5Attack) { CViperAsyncRequest *pViperReq = NULL; // if NULL was passed in, then start from the head of the queue. if (pLastReq == NULL) pLastReq = &m_ReqObjList; // lock the queue while we find the next candidate Lock(); if (fTestForF5Attack == TRUE) { // get the next request on the list pViperReq = static_cast(pLastReq->PPrev()); // enter a while loop until we either get to the end of the list or we find // a request has been posted to the viper queue while((pViperReq != &m_ReqObjList) && (pViperReq->FAsyncCallPosted() == FALSE)) pViperReq = static_cast(pViperReq->PPrev()); } else { // get the next request on the list pViperReq = static_cast(pLastReq->PNext()); // enter a while loop until we either get to the end of the list or we find // a request that hasn't been tested in dwTimeout() and has been posted // to the viper queue while((pViperReq != &m_ReqObjList) && ((pViperReq->SecsSinceLastTest() < pViperReq->dwTimeout()) || (pViperReq->FAsyncCallPosted() == FALSE))) pViperReq = static_cast(pViperReq->PNext()); } // if we found a candidate, set the TestingConnection flag if (pViperReq != &m_ReqObjList) { pViperReq->SetTestingConnection(); } else { // ended up at the queue head. Return NULL. pViperReq = NULL; } Unlock(); return pViperReq; } /*=================================================================== G l o b a l F u n c t i o n s ===================================================================*/ /*=================================================================== ViperSetContextProperty Static utility function. Set Viper context property by BSTR and IDispatch*. The real interface takes BSTR and VARIANT. Parameters IContextProperties *pContextProperties Context BSTR bstrPropertyName Name IDispatch *pdispPropertyValue Value Returns: HRESULT ===================================================================*/ static HRESULT ViperSetContextProperty ( IContextProperties *pContextProperties, BSTR bstrPropertyName, IDispatch *pdispPropertyValue ) { // Make VARIANT from IDispatch* pdispPropertyValue->AddRef(); VARIANT Variant; VariantInit(&Variant); V_VT(&Variant) = VT_DISPATCH; V_DISPATCH(&Variant) = pdispPropertyValue; // Call Viper to set the property HRESULT hr = pContextProperties->SetProperty ( bstrPropertyName, Variant ); // Cleanup VariantClear(&Variant); return hr; } /*=================================================================== ViperAttachIntrinsicsToContext Attach ASP intrinsic objects as Viper context properties Parameters - Intrinsics as interface pointers IApplicationObject *pAppln Application (required) ISessionObject *pSession Session (optional) IRequest *pRequest Request (optional) IResponse *pResponse Response (optional) IServer *pServer Server (optional) Returns: HRESULT ===================================================================*/ HRESULT ViperAttachIntrinsicsToContext ( IApplicationObject *pAppln, ISessionObject *pSession, IRequest *pRequest, IResponse *pResponse, IServer *pServer ) { Assert(pAppln); HRESULT hr = S_OK; // Get Viper Context IObjectContext *pViperContext = NULL; hr = GetObjectContext(&pViperContext); // Get IContextPoperties interface IContextProperties *pContextProperties = NULL; if (SUCCEEDED(hr)) hr = pViperContext->QueryInterface ( IID_IContextProperties, (void **)&pContextProperties ); // Set properties if (SUCCEEDED(hr)) hr = ViperSetContextProperty ( pContextProperties, BSTR_OBJ_APPLICATION, pAppln ); if (SUCCEEDED(hr) && pSession) hr = ViperSetContextProperty ( pContextProperties, BSTR_OBJ_SESSION, pSession ); if (SUCCEEDED(hr) && pRequest) hr = ViperSetContextProperty ( pContextProperties, BSTR_OBJ_REQUEST, pRequest ); if (SUCCEEDED(hr) && pResponse) hr = ViperSetContextProperty ( pContextProperties, BSTR_OBJ_RESPONSE, pResponse ); if (SUCCEEDED(hr) && pServer) hr = ViperSetContextProperty ( pContextProperties, BSTR_OBJ_SERVER, pServer ); // Cleanup if (pContextProperties) pContextProperties->Release(); if (pViperContext) pViperContext->Release(); return hr; } /*=================================================================== ViperGetObjectFromContext Get Viper context property by LPWSTR and return it as IDispatch*. The real interface takes BSTR and VARIANT. Parameters BSTR bstrName Property Name IDispatch **ppdisp [out] Object (Property Value) Returns: HRESULT ===================================================================*/ HRESULT ViperGetObjectFromContext ( BSTR bstrName, IDispatch **ppdisp ) { Assert(ppdisp); HRESULT hr = S_OK; // Get Viper Context IObjectContext *pViperContext = NULL; hr = GetObjectContext(&pViperContext); // Get IContextPoperties interface IContextProperties *pContextProperties = NULL; if (SUCCEEDED(hr)) hr = pViperContext->QueryInterface ( IID_IContextProperties, (void **)&pContextProperties ); // Get property Value as variant VARIANT Variant; VariantInit(&Variant); if (SUCCEEDED(hr)) hr = pContextProperties->GetProperty(bstrName, &Variant); // Convert Variant to IDispatch* if (SUCCEEDED(hr)) { IDispatch *pDisp = NULL; if (V_VT(&Variant) == VT_DISPATCH) pDisp = V_DISPATCH(&Variant); if (pDisp) { pDisp->AddRef(); *ppdisp = pDisp; } else hr = E_FAIL; } // Cleanup VariantClear(&Variant); if (pContextProperties) pContextProperties->Release(); if (pViperContext) pViperContext->Release(); if (FAILED(hr)) *ppdisp = NULL; return hr; } /*=================================================================== ViperGetHitObjFromContext Get Server from Viper context property and get it's current HitObj Parameters CHitObj **ppHitObj [out] Returns: HRESULT ===================================================================*/ HRESULT ViperGetHitObjFromContext ( CHitObj **ppHitObj ) { *ppHitObj = NULL; IDispatch *pdispServer = NULL; HRESULT hr = ViperGetObjectFromContext(BSTR_OBJ_SERVER, &pdispServer); if (FAILED(hr)) return hr; if (pdispServer) { CServer *pServer = static_cast(pdispServer); *ppHitObj = pServer->PHitObj(); pdispServer->Release(); } return *ppHitObj ? S_OK : S_FALSE; } /*=================================================================== ViperCreateInstance Viper's implementation of CoCreateInstance Parameters REFCLSID rclsid class id REFIID riid interface void **ppv [out] pointer to interface Returns: HRESULT ===================================================================*/ HRESULT ViperCreateInstance ( REFCLSID rclsid, REFIID riid, void **ppv ) { /* DWORD dwClsContext = (Glob(fAllowOutOfProcCmpnts)) ? CLSCTX_INPROC_SERVER | CLSCTX_SERVER : CLSCTX_INPROC_SERVER; */ // The reasons for supporting ASPAllowOutOfProcComponents seem to have // vanished. Because this only partially worked in II4 and we changed // the default in IIS5 this was causing problems with upgrades. So // we're going to ignore the fAllowOutOfProcCmpnts setting. DWORD dwClsContext = CLSCTX_INPROC_SERVER | CLSCTX_SERVER; return CoCreateInstance(rclsid, NULL, dwClsContext, riid, ppv); } /*=================================================================== ViperConfigure Viper settings: # of threads, queue len, in-proc failfast, allow oop components Parameters Returns: HRESULT ===================================================================*/ HRESULT ViperConfigure() { HRESULT hr = S_OK; IMTSPackage *pPackage = NULL; // // Get hold of the package // hr = CoCreateInstance(CLSID_MTSPackage, NULL, CLSCTX_INPROC_SERVER, IID_IMTSPackage, (void **)&pPackage); if (SUCCEEDED(hr) && !pPackage) hr = E_FAIL; // // Bug 111008: Tell Viper that we do impersonations // if (SUCCEEDED(hr)) { IImpersonationControl *pImpControl = NULL; hr = pPackage->QueryInterface(IID_IImpersonationControl, (void **)&pImpControl); if (SUCCEEDED(hr) && pImpControl) { hr = pImpControl->ClientsImpersonate(TRUE); pImpControl->Release(); } } // // Disable FAILFAST for in-proc case // if (SUCCEEDED(hr) && !g_fOOP) { IFailfastControl *pFFControl = NULL; hr = pPackage->QueryInterface(IID_IFailfastControl, (void **)&pFFControl); if (SUCCEEDED(hr) && pFFControl) { pFFControl->SetApplFailfast(FALSE); pFFControl->Release(); } } /* // // Set Allow OOP Components // if (SUCCEEDED(hr)) { INonMTSActivation *pNonMTSActivation = NULL; hr = pPackage->QueryInterface(IID_INonMTSActivation, (void **)&pNonMTSActivation); if (SUCCEEDED(hr) && pNonMTSActivation) { pNonMTSActivation->OutOfProcActivationAllowed(fAllowOopComponents); pNonMTSActivation->Release(); } } */ // // Clean-up // if (pPackage) pPackage->Release(); return hr; } HRESULT ViperConfigureMTA() { HRESULT hr = S_OK; IMTSPackage *pPackage = NULL; if (g_fFirstMTAHit) { EnterCriticalSection(&g_csFirstMTAHitLock); if (g_fFirstMTAHit) { // // Get hold of the package // hr = CoCreateInstance(CLSID_MTSPackage, NULL, CLSCTX_INPROC_SERVER, IID_IMTSPackage, (void **)&pPackage); if (SUCCEEDED(hr) && !pPackage) hr = E_FAIL; if (SUCCEEDED(hr)) { IComMtaThreadPoolKnobs *pMTAKnobs = NULL; hr = pPackage->QueryInterface(IID_IComMtaThreadPoolKnobs, (void **)&pMTAKnobs); if (SUCCEEDED(hr) && pMTAKnobs) { SYSTEM_INFO si; GetSystemInfo(&si); DWORD dwThreadCount; if (FAILED(g_AspRegistryParams.GetTotalThreadMax(&dwThreadCount))) { dwThreadCount = DEFAULT_MAX_THREAD; } if (dwThreadCount > Glob(dwProcessorThreadMax)*si.dwNumberOfProcessors) { dwThreadCount = Glob(dwProcessorThreadMax)*si.dwNumberOfProcessors; } hr = pMTAKnobs->MTASetMaxThreadCount(dwThreadCount); if (SUCCEEDED(hr)) hr = pMTAKnobs->MTASetThrottleValue(8); pMTAKnobs->Release(); } } g_fFirstMTAHit = FALSE; } LeaveCriticalSection(&g_csFirstMTAHitLock); } return hr; } HRESULT ViperConfigureSTA() { HRESULT hr = S_OK; IMTSPackage *pPackage = NULL; if (g_fFirstSTAHit) { EnterCriticalSection(&g_csFirstSTAHitLock); if (g_fFirstSTAHit) { // // Get hold of the package // hr = CoCreateInstance(CLSID_MTSPackage, NULL, CLSCTX_INPROC_SERVER, IID_IMTSPackage, (void **)&pPackage); if (SUCCEEDED(hr) && !pPackage) hr = E_FAIL; // // Set knobs // if (SUCCEEDED(hr)) { IComStaThreadPoolKnobs *pKnobs = NULL; hr = pPackage->QueryInterface(IID_IComStaThreadPoolKnobs, (void **)&pKnobs); if (SUCCEEDED(hr) && pKnobs) { // number of threads SYSTEM_INFO si; GetSystemInfo(&si); DWORD dwThreadCount; DWORD dwMinThreads; if (FAILED(g_AspRegistryParams.GetTotalThreadMax(&dwThreadCount))) { dwThreadCount = DEFAULT_MAX_THREAD; } if (dwThreadCount > Glob(dwProcessorThreadMax)*si.dwNumberOfProcessors) { dwThreadCount = Glob(dwProcessorThreadMax)*si.dwNumberOfProcessors; } dwMinThreads = (si.dwNumberOfProcessors > 4) ? si.dwNumberOfProcessors : 4; if (dwThreadCount < dwMinThreads) { dwThreadCount = dwMinThreads; } pKnobs->SetMaxThreadCount(dwThreadCount); pKnobs->SetMinThreadCount(dwMinThreads); // queue length pKnobs->SetQueueDepth(30000); pKnobs->SetActivityPerThread(1); pKnobs->Release(); } } // set Knobs2 if (SUCCEEDED(hr)) { IComStaThreadPoolKnobs2 *pKnobs2 = NULL; hr = pPackage->QueryInterface(IID_IComStaThreadPoolKnobs2, (void **)&pKnobs2); if (SUCCEEDED(hr) && pKnobs2) { DWORD dwMaxCSR; DWORD dwMaxCPU; DWORD dwDisableCpuMetric; if (SUCCEEDED(g_AspRegistryParams.GetMaxCPU(&dwMaxCPU))) { pKnobs2->SetMaxCPULoad(dwMaxCPU); } else { pKnobs2->SetMaxCPULoad(100); } if (SUCCEEDED(g_AspRegistryParams.GetMaxCSR(&dwMaxCSR))) { pKnobs2->SetMaxCSR(dwMaxCSR); } if (SUCCEEDED(g_AspRegistryParams.GetDisableComPlusCpuMetric(&dwDisableCpuMetric))) { pKnobs2->SetCPUMetricEnabled(!dwDisableCpuMetric); } pKnobs2->Release(); } } g_fFirstSTAHit = FALSE; } LeaveCriticalSection(&g_csFirstSTAHitLock); } return hr; } /*=================================================================== C O M H e l p e r A P I ===================================================================*/ /*=================================================================== ViperCoObjectIsaProxy Checks if the given IUnknown* points to a proxy Parameters IUnknown* pUnk pointer to check Returns: BOOL (TRUE if Proxy) ===================================================================*/ BOOL ViperCoObjectIsaProxy ( IUnknown* pUnk ) { HRESULT hr; IUnknown *pUnk2; hr = pUnk->QueryInterface(IID_IProxyManager, (void**)&pUnk2); if (FAILED(hr)) return FALSE; pUnk2->Release(); return TRUE; } /*=================================================================== ViperCoObjectAggregatesFTM Checks if the given object agregates free threaded marshaller (is agile) Parameters IUnknown* pUnk pointer to check Returns: BOOL (TRUE if Agile) ===================================================================*/ BOOL ViperCoObjectAggregatesFTM ( IUnknown *pUnk ) { HRESULT hr; IMarshal *pMarshal; GUID guidClsid; hr = pUnk->QueryInterface(IID_IMarshal, (void**)&pMarshal); if (FAILED(hr)) return FALSE; hr = pMarshal->GetUnmarshalClass(IID_IUnknown, pUnk, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL, &guidClsid); pMarshal->Release(); if (FAILED(hr)) return FALSE; return (guidClsid == CLSID_InProcFreeMarshaler); }