aspnetcore/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp

343 lines
11 KiB
C++

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "applicationmanager.h"
#include "proxymodule.h"
#include "utility.h"
#include "resources.h"
#include "SRWExclusiveLock.h"
#include "exceptions.h"
#include "EventLog.h"
extern BOOL g_fInShutdown;
// The application manager is a singleton across ANCM.
APPLICATION_MANAGER* APPLICATION_MANAGER::sm_pApplicationManager = NULL;
//
// Retrieves the application info from the application manager
// Will create the application info if it isn't initalized
//
HRESULT
APPLICATION_MANAGER::GetOrCreateApplicationInfo(
_In_ IHttpContext* pHttpContext,
_Out_ APPLICATION_INFO ** ppApplicationInfo
)
{
HRESULT hr = S_OK;
APPLICATION_INFO *pApplicationInfo = NULL;
BOOL fMixedHostingModelError = FALSE;
BOOL fDuplicatedInProcessApp = FALSE;
PCWSTR pszApplicationId = NULL;
APP_HOSTING_MODEL hostingModel = HOSTING_UNKNOWN;
STACK_STRU ( strEventMsg, 256 );
DBG_ASSERT(pHttpContext);
DBG_ASSERT(ppApplicationInfo);
*ppApplicationInfo = NULL;
IHttpApplication &pApplication = *pHttpContext->GetApplication();
// The configuration path is unique for each application and is used for the
// key in the applicationInfoHash.
pszApplicationId = pApplication.GetApplicationId();
// When accessing the m_pApplicationInfoHash, we need to acquire the application manager
// lock to avoid races on setting state.
SRWSharedLock lock(m_srwLock);
if (!m_fDebugInitialize)
{
DebugInitializeFromConfig(m_pHttpServer, pApplication);
m_fDebugInitialize = TRUE;
}
if (g_fInShutdown)
{
FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS));
}
m_pApplicationInfoHash->FindKey(pszApplicationId, ppApplicationInfo);
if (*ppApplicationInfo == NULL)
{
pApplicationInfo = new APPLICATION_INFO(m_pHttpServer);
FINISHED_IF_FAILED(pApplicationInfo->Initialize(pApplication));
hostingModel = pApplicationInfo->QueryConfig()->QueryHostingModel();
if (m_pApplicationInfoHash->Count() == 0)
{
m_hostingModel = hostingModel;
pApplicationInfo->MarkValid();
}
else
{
if (hostingModel == HOSTING_OUT_PROCESS && hostingModel == m_hostingModel)
{
pApplicationInfo->MarkValid();
}
else
{
if (hostingModel != m_hostingModel)
{
fMixedHostingModelError = TRUE;
}
else
{
fDuplicatedInProcessApp = TRUE;
}
}
}
FINISHED_IF_FAILED(m_pApplicationInfoHash->InsertRecord(pApplicationInfo));
*ppApplicationInfo = pApplicationInfo;
pApplicationInfo = NULL;
}
Finished:
// log the error
if (fDuplicatedInProcessApp)
{
UTILITY::LogEventF(g_hEventLog,
EVENTLOG_ERROR_TYPE,
ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP,
ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG,
pszApplicationId);
}
else if (fMixedHostingModelError)
{
UTILITY::LogEventF(g_hEventLog,
EVENTLOG_ERROR_TYPE,
ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR,
ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG,
pszApplicationId,
hostingModel);
}
if (pApplicationInfo != NULL)
{
pApplicationInfo->DereferenceApplicationInfo();
pApplicationInfo = NULL;
}
return hr;
}
//
// If the application's configuration was changed,
// append the configuration path to the config change context.
//
BOOL
APPLICATION_MANAGER::FindConfigChangedApplication(
_In_ APPLICATION_INFO * pEntry,
_In_ PVOID pvContext)
{
DBG_ASSERT(pEntry);
DBG_ASSERT(pvContext);
// Config Change context contains the original config path that changed
// and a multiStr containing
CONFIG_CHANGE_CONTEXT* pContext = static_cast<CONFIG_CHANGE_CONTEXT*>(pvContext);
STRU* pstruConfigPath = pEntry->QueryConfig()->QueryConfigPath();
// check if the application path contains our app/subapp by seeing if the config path
// starts with the notification path.
BOOL fChanged = pstruConfigPath->StartsWith(pContext->pstrPath, true);
if (fChanged)
{
DWORD dwLen = (DWORD)wcslen(pContext->pstrPath);
WCHAR wChar = pstruConfigPath->QueryStr()[dwLen];
// We need to check that the last character of the config path
// is either a null terminator or a slash.
// This checks the case where the config path was
// MACHINE/WEBROOT/site and your site path is MACHINE/WEBROOT/siteTest
if (wChar != L'\0' && wChar != L'/')
{
// not current app or sub app
fChanged = FALSE;
}
else
{
pContext->MultiSz.Append(pEntry->QueryApplicationInfoKey());
}
}
return fChanged;
}
//
// Finds any applications affected by a configuration change and calls Recycle on them
// InProcess: Triggers g_httpServer->RecycleProcess() and keep the application inside of the manager.
// This will cause a shutdown event to occur through the global stop listening event.
// OutOfProcess: Removes all applications in the application manager and calls Recycle, which will call Shutdown,
// on each application.
//
HRESULT
APPLICATION_MANAGER::RecycleApplicationFromManager(
_In_ LPCWSTR pszApplicationId
)
{
HRESULT hr = S_OK;
DWORD dwPreviousCounter = 0;
APPLICATION_INFO_HASH* table = NULL;
CONFIG_CHANGE_CONTEXT context;
if (g_fInShutdown)
{
// We are already shutting down, ignore this event as a global configuration change event
// can occur after global stop listening for some reason.
return hr;
}
{
SRWExclusiveLock lock(m_srwLock);
if (g_fInShutdown)
{
return hr;
}
// Make a shallow copy of existing hashtable as we may need to remove nodes
// This will be used for finding differences in which applications are affected by a config change.
table = new APPLICATION_INFO_HASH();
// few application expected, small bucket size for hash table
if (FAILED(hr = table->Initialize(17 /*prime*/)))
{
goto Finished;
}
context.pstrPath = pszApplicationId;
// Keep track of the preview count of applications to know whether there are applications to delete
dwPreviousCounter = m_pApplicationInfoHash->Count();
// We don't want to hold the application manager lock for long time as it will block all incoming requests
// Don't call application shutdown inside the lock
m_pApplicationInfoHash->Apply(APPLICATION_INFO_HASH::ReferenceCopyToTable, static_cast<PVOID>(table));
DBG_ASSERT(dwPreviousCounter == table->Count());
// Removed the applications which are impacted by the configurtion change
m_pApplicationInfoHash->DeleteIf(FindConfigChangedApplication, (PVOID)&context);
if (m_pApplicationInfoHash->Count() == 0 && m_hostingModel == HOSTING_OUT_PROCESS)
{
// reuse current process
m_hostingModel = HOSTING_UNKNOWN;
}
}
// If we receive a request at this point.
// OutOfProcess: we will create a new application with new configuration
// InProcess: the request would have to be rejected, as we are about to call g_HttpServer->RecycleProcess
// on the worker proocess
if (!context.MultiSz.IsEmpty())
{
PCWSTR path = context.MultiSz.First();
// Iterate through each of the paths that were shut down,
// calling RecycleApplication on each of them.
while (path != NULL)
{
APPLICATION_INFO* pRecord;
// Application got recycled. Log an event
STACK_STRU(strEventMsg, 256);
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG,
path)))
{
UTILITY::LogEvent(g_hEventLog,
EVENTLOG_INFORMATION_TYPE,
ASPNETCORE_EVENT_RECYCLE_CONFIGURATION,
strEventMsg.QueryStr());
}
table->FindKey(path, &pRecord);
DBG_ASSERT(pRecord != NULL);
// RecycleApplication is called on a separate thread.
pRecord->RecycleApplication();
pRecord->DereferenceApplicationInfo();
path = context.MultiSz.Next(path);
}
}
Finished:
if (table != NULL)
{
table->Clear();
delete table;
}
if (FAILED(hr))
{
// Failed to recycle an application. Log an event
STACK_STRU(strEventMsg, 256);
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG,
pszApplicationId)))
{
UTILITY::LogEvent(g_hEventLog,
EVENTLOG_ERROR_TYPE,
ASPNETCORE_EVENT_RECYCLE_APP_FAILURE,
strEventMsg.QueryStr());
}
// Need to recycle the process as we cannot recycle the application
if (!g_fRecycleProcessCalled)
{
g_fRecycleProcessCalled = TRUE;
m_pHttpServer.RecycleProcess(L"AspNetCore Recycle Process on Demand Due Application Recycle Error");
}
}
return hr;
}
//
// Shutsdown all applications in the application hashtable
// Only called by OnGlobalStopListening.
//
VOID
APPLICATION_MANAGER::ShutDown()
{
// We are guaranteed to only have one outstanding OnGlobalStopListening event at a time
// However, it is possible to receive multiple OnGlobalStopListening events
// Protect against this by checking if we already shut down.
g_fInShutdown = TRUE;
if (m_pApplicationInfoHash != NULL)
{
DBG_ASSERT(m_pApplicationInfoHash);
// During shutdown we lock until we delete the application
SRWExclusiveLock lock(m_srwLock);
// Call shutdown on each application in the application manager
m_pApplicationInfoHash->Apply(ShutdownApplication, NULL);
m_pApplicationInfoHash->Clear();
delete m_pApplicationInfoHash;
m_pApplicationInfoHash = NULL;
}
}
//
// Calls shutdown on each application. ApplicationManager's lock is held for duration of
// each shutdown call, guaranteeing another application cannot be created.
//
// static
VOID
APPLICATION_MANAGER::ShutdownApplication(
_In_ APPLICATION_INFO * pEntry,
_In_ PVOID pvContext
)
{
UNREFERENCED_PARAMETER(pvContext);
DBG_ASSERT(pEntry != NULL);
pEntry->ShutDownApplication();
}