change the logic on handling configuration change and adds shutdown logic (#610)
This commit is contained in:
parent
49feb2534d
commit
c833e7b994
|
|
@ -134,6 +134,20 @@ public:
|
|||
return m_pApplication;
|
||||
}
|
||||
|
||||
VOID
|
||||
ClearAndDereferenceApplication()
|
||||
{
|
||||
APPLICATION* pApplication = m_pApplication;
|
||||
m_pApplication = NULL;
|
||||
pApplication->DereferenceApplication();
|
||||
}
|
||||
|
||||
VOID
|
||||
ClearApplication()
|
||||
{
|
||||
m_pApplication = NULL;
|
||||
}
|
||||
|
||||
HRESULT
|
||||
EnsureApplicationCreated();
|
||||
|
||||
|
|
@ -211,6 +225,18 @@ public:
|
|||
pApplicationInfo->DereferenceApplicationInfo();
|
||||
}
|
||||
|
||||
static
|
||||
VOID
|
||||
ReferenceCopyToTable(
|
||||
APPLICATION_INFO * pEntry,
|
||||
PVOID pvData
|
||||
)
|
||||
{
|
||||
APPLICATION_INFO_HASH *pHash = static_cast<APPLICATION_INFO_HASH *>(pvData);
|
||||
DBG_ASSERT(pHash);
|
||||
pHash->InsertRecord(pEntry);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
APPLICATION_INFO_HASH(const APPLICATION_INFO_HASH &);
|
||||
|
|
|
|||
|
|
@ -3,13 +3,20 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#define DEFAULT_HASH_BUCKETS 293
|
||||
#define DEFAULT_HASH_BUCKETS 17
|
||||
|
||||
//
|
||||
// This class will manage the lifecycle of all Asp.Net Core applciation
|
||||
// It should be global singleton.
|
||||
// Should always call GetInstance to get the object instance
|
||||
//
|
||||
|
||||
struct CONFIG_CHANGE_CONTEXT
|
||||
{
|
||||
PCWSTR pstrPath;
|
||||
MULTISZ MultiSz;
|
||||
};
|
||||
|
||||
class APPLICATION_MANAGER
|
||||
{
|
||||
public:
|
||||
|
|
@ -41,15 +48,42 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
static
|
||||
BOOL
|
||||
FindConfigChangedApplication(
|
||||
_In_ APPLICATION_INFO * pEntry,
|
||||
_In_ PVOID pvContext
|
||||
);
|
||||
|
||||
static
|
||||
VOID
|
||||
RecycleApplication(
|
||||
_In_ APPLICATION_INFO * pEntry,
|
||||
_In_ APP_HOSTING_MODEL hostingModel
|
||||
);
|
||||
|
||||
static
|
||||
void
|
||||
DoRecycleApplication(
|
||||
LPVOID lpParam
|
||||
);
|
||||
|
||||
static
|
||||
VOID
|
||||
ShutdownApplication(
|
||||
_In_ APPLICATION_INFO * pEntry,
|
||||
_In_ PVOID pvContext
|
||||
);
|
||||
|
||||
HRESULT
|
||||
GetApplicationInfo(
|
||||
GetOrCreateApplicationInfo(
|
||||
_In_ IHttpServer* pServer,
|
||||
_In_ ASPNETCORE_CONFIG* pConfig,
|
||||
_Out_ APPLICATION_INFO ** ppApplicationInfo
|
||||
);
|
||||
|
||||
HRESULT
|
||||
RecycleApplication(
|
||||
RecycleApplicationFromManager(
|
||||
_In_ LPCWSTR pszApplicationId
|
||||
);
|
||||
|
||||
|
|
@ -120,8 +154,7 @@ private:
|
|||
//
|
||||
APPLICATION_MANAGER() : m_pApplicationInfoHash(NULL),
|
||||
m_pFileWatcher(NULL),
|
||||
m_hostingModel(HOSTING_UNKNOWN),
|
||||
m_fInShutdown(FALSE)
|
||||
m_hostingModel(HOSTING_UNKNOWN)
|
||||
{
|
||||
InitializeSRWLock(&m_srwLock);
|
||||
}
|
||||
|
|
@ -131,5 +164,4 @@ private:
|
|||
static APPLICATION_MANAGER *sm_pApplicationManager;
|
||||
SRWLOCK m_srwLock;
|
||||
APP_HOSTING_MODEL m_hostingModel;
|
||||
bool m_fInShutdown;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ public:
|
|||
);
|
||||
|
||||
GLOBAL_NOTIFICATION_STATUS
|
||||
OnGlobalApplicationStop(
|
||||
_In_ IHttpApplicationStopProvider * pProvider
|
||||
OnGlobalConfigurationChange(
|
||||
_In_ IGlobalConfigurationChangeProvider * pProvider
|
||||
);
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -89,6 +89,10 @@ APPLICATION_INFO::StartMonitoringAppOffline()
|
|||
return hr;
|
||||
}
|
||||
|
||||
//
|
||||
// Called by the file watcher when the app_offline.htm's file status has been changed.
|
||||
// If it finds it, we will call recycle on the application.
|
||||
//
|
||||
VOID
|
||||
APPLICATION_INFO::UpdateAppOfflineFileHandle()
|
||||
{
|
||||
|
|
@ -98,15 +102,30 @@ APPLICATION_INFO::UpdateAppOfflineFileHandle()
|
|||
&strFilePath);
|
||||
APP_OFFLINE_HTM *pOldAppOfflineHtm = NULL;
|
||||
APP_OFFLINE_HTM *pNewAppOfflineHtm = NULL;
|
||||
BOOL fLocked = FALSE;
|
||||
|
||||
if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(strFilePath.QueryStr()) &&
|
||||
GetLastError() == ERROR_FILE_NOT_FOUND)
|
||||
{
|
||||
// Check if app offline was originally present.
|
||||
// if it was, log that app_offline has been dropped.
|
||||
if (m_fAppOfflineFound)
|
||||
{
|
||||
STACK_STRU(strEventMsg, 256);
|
||||
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED_MSG)))
|
||||
{
|
||||
UTILITY::LogEvent(g_hEventLog,
|
||||
EVENTLOG_INFORMATION_TYPE,
|
||||
ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED,
|
||||
strEventMsg.QueryStr());
|
||||
}
|
||||
}
|
||||
|
||||
m_fAppOfflineFound = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fAppOfflineFound = TRUE;
|
||||
pNewAppOfflineHtm = new APP_OFFLINE_HTM(strFilePath.QueryStr());
|
||||
|
||||
if (pNewAppOfflineHtm != NULL)
|
||||
|
|
@ -132,9 +151,18 @@ APPLICATION_INFO::UpdateAppOfflineFileHandle()
|
|||
}
|
||||
}
|
||||
|
||||
m_fAppOfflineFound = TRUE;
|
||||
|
||||
// recycle the application
|
||||
if (m_pApplication != NULL)
|
||||
{
|
||||
// Lock here to avoid races with the application manager calling shutdown on the application.
|
||||
AcquireSRWLockExclusive(&m_srwLock);
|
||||
fLocked = TRUE;
|
||||
if (m_pApplication == NULL)
|
||||
{
|
||||
goto Finished;
|
||||
}
|
||||
STACK_STRU(strEventMsg, 256);
|
||||
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG,
|
||||
|
|
@ -146,12 +174,16 @@ APPLICATION_INFO::UpdateAppOfflineFileHandle()
|
|||
strEventMsg.QueryStr());
|
||||
}
|
||||
|
||||
m_pApplication->ShutDown();
|
||||
m_pApplication->DereferenceApplication();
|
||||
m_pApplication = NULL;
|
||||
APPLICATION_MANAGER::RecycleApplication(this, m_pConfiguration->QueryHostingModel());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Finished:
|
||||
if (fLocked)
|
||||
{
|
||||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT
|
||||
|
|
|
|||
|
|
@ -3,10 +3,15 @@
|
|||
|
||||
#include "precomp.hxx"
|
||||
|
||||
// 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::GetApplicationInfo(
|
||||
APPLICATION_MANAGER::GetOrCreateApplicationInfo(
|
||||
_In_ IHttpServer* pServer,
|
||||
_In_ ASPNETCORE_CONFIG* pConfig,
|
||||
_Out_ APPLICATION_INFO ** ppApplicationInfo
|
||||
|
|
@ -21,21 +26,25 @@ APPLICATION_MANAGER::GetApplicationInfo(
|
|||
PCWSTR pszApplicationId = NULL;
|
||||
STACK_STRU ( strEventMsg, 256 );
|
||||
|
||||
DBG_ASSERT(pServer);
|
||||
DBG_ASSERT(pConfig);
|
||||
DBG_ASSERT(ppApplicationInfo);
|
||||
|
||||
*ppApplicationInfo = NULL;
|
||||
|
||||
DBG_ASSERT(pServer != NULL);
|
||||
DBG_ASSERT(pConfig != NULL);
|
||||
|
||||
// The configuration path is unique for each application and is used for the
|
||||
// key in the applicationInfoHash.
|
||||
pszApplicationId = pConfig->QueryConfigPath()->QueryStr();
|
||||
|
||||
hr = key.Initialize(pszApplicationId);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
// When accessing the m_pApplicationInfoHash, we need to acquire the application manager
|
||||
// lock to avoid races on setting state.
|
||||
AcquireSRWLockShared(&m_srwLock);
|
||||
if (m_fInShutdown)
|
||||
if (g_fInShutdown)
|
||||
{
|
||||
ReleaseSRWLockShared(&m_srwLock);
|
||||
hr = HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS);
|
||||
|
|
@ -46,6 +55,7 @@ APPLICATION_MANAGER::GetApplicationInfo(
|
|||
|
||||
if (*ppApplicationInfo == NULL)
|
||||
{
|
||||
// Check which hosting model we want to support
|
||||
switch (pConfig->QueryHostingModel())
|
||||
{
|
||||
case HOSTING_IN_PROCESS:
|
||||
|
|
@ -74,7 +84,7 @@ APPLICATION_MANAGER::GetApplicationInfo(
|
|||
|
||||
AcquireSRWLockExclusive(&m_srwLock);
|
||||
fExclusiveLock = TRUE;
|
||||
if (m_fInShutdown)
|
||||
if (g_fInShutdown)
|
||||
{
|
||||
// Already in shuting down. No need to create the application
|
||||
hr = HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS);
|
||||
|
|
@ -124,10 +134,12 @@ APPLICATION_MANAGER::GetApplicationInfo(
|
|||
}
|
||||
|
||||
*ppApplicationInfo = pApplicationInfo;
|
||||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
fExclusiveLock = FALSE;
|
||||
|
||||
pApplicationInfo->StartMonitoringAppOffline();
|
||||
|
||||
// Release the locko after starting to monitor for app offline to avoid races with it being dropped.
|
||||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
|
||||
fExclusiveLock = FALSE;
|
||||
pApplicationInfo = NULL;
|
||||
}
|
||||
|
||||
|
|
@ -189,39 +201,131 @@ Finished:
|
|||
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(*pstruConfigPath);
|
||||
}
|
||||
}
|
||||
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::RecycleApplication(
|
||||
APPLICATION_MANAGER::RecycleApplicationFromManager(
|
||||
_In_ LPCWSTR pszApplicationId
|
||||
)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
APPLICATION_INFO_KEY key;
|
||||
DWORD dwPreviousCounter = 0;
|
||||
HRESULT hr = S_OK;
|
||||
APPLICATION_INFO_KEY key;
|
||||
DWORD dwPreviousCounter = 0;
|
||||
APPLICATION_INFO_HASH* table = NULL;
|
||||
CONFIG_CHANGE_CONTEXT context;
|
||||
BOOL fKeepTable = FALSE;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
AcquireSRWLockExclusive(&m_srwLock);
|
||||
if (g_fInShutdown)
|
||||
{
|
||||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = key.Initialize(pszApplicationId);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto Finished;
|
||||
}
|
||||
AcquireSRWLockExclusive(&m_srwLock);
|
||||
// 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();
|
||||
|
||||
if (table == NULL)
|
||||
{
|
||||
hr = E_OUTOFMEMORY;
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
m_pApplicationInfoHash->DeleteKey(&key);
|
||||
// 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(dwPreviousCounter != m_pApplicationInfoHash->Count())
|
||||
if (dwPreviousCounter != m_pApplicationInfoHash->Count())
|
||||
{
|
||||
// Application got recycled. Log an event
|
||||
STACK_STRU(strEventMsg, 256);
|
||||
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG,
|
||||
pszApplicationId)))
|
||||
if (m_hostingModel == HOSTING_IN_PROCESS)
|
||||
{
|
||||
UTILITY::LogEvent(g_hEventLog,
|
||||
EVENTLOG_INFORMATION_TYPE,
|
||||
ASPNETCORE_EVENT_RECYCLE_CONFIGURATION,
|
||||
strEventMsg.QueryStr());
|
||||
}
|
||||
// When we are inprocess, we need to keep the application the
|
||||
// application manager that is being deleted. This is because we will always need to recycle the worker
|
||||
// process and any requests that hit this worker process must be rejected (while out of process can
|
||||
// start a new dotnet process). We will immediately call Recycle after this call.
|
||||
DBG_ASSERT(m_pApplicationInfoHash->Count() == 0);
|
||||
delete m_pApplicationInfoHash;
|
||||
|
||||
// We don't want to delete the table as m_pApplicationInfoHash = table
|
||||
fKeepTable = TRUE;
|
||||
m_pApplicationInfoHash = table;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pApplicationInfoHash->Count() == 0)
|
||||
|
|
@ -229,40 +333,178 @@ APPLICATION_MANAGER::RecycleApplication(
|
|||
m_hostingModel = HOSTING_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
if (g_fAspnetcoreRHLoadedError)
|
||||
{
|
||||
// We had assembly loading failure
|
||||
// this error blocked the start of all applications
|
||||
// Let's recycle the worker process if user redeployed any application
|
||||
g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand due to assembly loading failure");
|
||||
}
|
||||
|
||||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
hr = key.Initialize(path);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
table->FindKey(&key, &pRecord);
|
||||
DBG_ASSERT(pRecord != NULL);
|
||||
|
||||
// RecycleApplication is called on a separate thread.
|
||||
RecycleApplication(pRecord, m_hostingModel);
|
||||
pRecord->DereferenceApplicationInfo();
|
||||
path = context.MultiSz.Next(path);
|
||||
}
|
||||
}
|
||||
|
||||
Finished:
|
||||
if (table != NULL && !fKeepTable)
|
||||
{
|
||||
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
|
||||
g_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()
|
||||
{
|
||||
m_fInShutdown = TRUE;
|
||||
// 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)
|
||||
{
|
||||
if (m_pFileWatcher != NULL)
|
||||
{
|
||||
delete m_pFileWatcher;
|
||||
m_pFileWatcher = NULL;
|
||||
}
|
||||
|
||||
DBG_ASSERT(m_pApplicationInfoHash);
|
||||
// During shutdown we lock until we delete the application
|
||||
AcquireSRWLockExclusive(&m_srwLock);
|
||||
|
||||
// clean up the hash table so that the application will be informed on shutdown
|
||||
// Call shutdown on each application in the application manager
|
||||
m_pApplicationInfoHash->Apply(ShutdownApplication, NULL);
|
||||
m_pApplicationInfoHash->Clear();
|
||||
delete m_pApplicationInfoHash;
|
||||
m_pApplicationInfoHash = NULL;
|
||||
|
||||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
}
|
||||
|
||||
// stop filewatcher monitoring thread
|
||||
if (m_pFileWatcher != NULL)
|
||||
{
|
||||
delete m_pFileWatcher;
|
||||
m_pFileWatcher = 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);
|
||||
APPLICATION* pApplication = pEntry->QueryApplication();
|
||||
pApplication->ReferenceApplication();
|
||||
|
||||
// Remove the application from the applicationInfo.
|
||||
pEntry->ClearAndDereferenceApplication();
|
||||
pApplication->ShutDown();
|
||||
pApplication->DereferenceApplication();
|
||||
}
|
||||
|
||||
//
|
||||
// Function used by DoRecycleApplication thread to do the real shutdown
|
||||
//
|
||||
// static
|
||||
VOID
|
||||
APPLICATION_MANAGER::DoRecycleApplication(
|
||||
LPVOID lpParam)
|
||||
{
|
||||
APPLICATION* pApplication = static_cast<APPLICATION*>(lpParam);
|
||||
|
||||
// Recycle will call shutdown for out of process
|
||||
pApplication->Recycle();
|
||||
|
||||
// Decrement the ref count as we reference it in RecycleApplication.
|
||||
pApplication->DereferenceApplication();
|
||||
}
|
||||
|
||||
//
|
||||
// Function used to recycle an application
|
||||
//
|
||||
// static
|
||||
VOID
|
||||
APPLICATION_MANAGER::RecycleApplication(
|
||||
_In_ APPLICATION_INFO * pEntry,
|
||||
_In_ APP_HOSTING_MODEL hostingModel
|
||||
)
|
||||
{
|
||||
|
||||
APPLICATION* pApplication = pEntry->QueryApplication();
|
||||
|
||||
// Reference the application first
|
||||
pApplication->ReferenceApplication();
|
||||
|
||||
if (hostingModel == APP_HOSTING_MODEL::HOSTING_OUT_PROCESS)
|
||||
{
|
||||
pEntry->ClearApplication();
|
||||
}
|
||||
|
||||
// Reset application pointer to NULL
|
||||
// The destructor of ApplictionInfo will not call ShutDown again
|
||||
HANDLE hThread = CreateThread(
|
||||
NULL, // default security attributes
|
||||
0, // default stack size
|
||||
(LPTHREAD_START_ROUTINE)DoRecycleApplication,
|
||||
pApplication, // thread function arguments
|
||||
0, // default creation flags
|
||||
NULL); // receive thread identifier
|
||||
|
||||
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ HINSTANCE g_hModule;
|
|||
HMODULE g_hAspnetCoreRH = NULL;
|
||||
BOOL g_fAspnetcoreRHAssemblyLoaded = FALSE;
|
||||
BOOL g_fAspnetcoreRHLoadedError = FALSE;
|
||||
BOOL g_fInShutdown = FALSE;
|
||||
DWORD g_dwAspNetCoreDebugFlags = 0;
|
||||
DWORD g_dwActiveServerProcesses = 0;
|
||||
SRWLOCK g_srwLock;
|
||||
|
|
@ -47,6 +48,10 @@ BOOL WINAPI DllMain(HMODULE hModule,
|
|||
DisableThreadLibraryCalls(hModule);
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
// IIS can cause dll detatch to occur before we receive global notifications
|
||||
// For example, when we switch the bitness of the worker process,
|
||||
// this is a bug in IIS. To try to avoid AVs, we will set a global flag
|
||||
g_fInShutdown = TRUE;
|
||||
StaticCleanup();
|
||||
default:
|
||||
break;
|
||||
|
|
@ -137,6 +142,8 @@ HRESULT
|
|||
{
|
||||
fDisableANCM = (dwData != 0);
|
||||
}
|
||||
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
if (fDisableANCM)
|
||||
|
|
@ -202,7 +209,7 @@ HRESULT
|
|||
|
||||
hr = pModuleInfo->SetGlobalNotifications(
|
||||
pGlobalModule,
|
||||
GL_APPLICATION_STOP | // Configuration change trigers IIS application stop
|
||||
GL_CONFIGURATION_CHANGE | // Configuration change trigers IIS application stop
|
||||
GL_STOP_LISTENING); // worker process stop or recycle
|
||||
|
||||
if (FAILED(hr))
|
||||
|
|
|
|||
|
|
@ -17,14 +17,18 @@ ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening(
|
|||
{
|
||||
UNREFERENCED_PARAMETER(pProvider);
|
||||
|
||||
if (m_pApplicationManager != NULL)
|
||||
if (g_fInShutdown)
|
||||
{
|
||||
// we should let application manager to shudown all allication
|
||||
// and dereference it as some requests may still reference to application manager
|
||||
m_pApplicationManager->ShutDown();
|
||||
m_pApplicationManager = NULL;
|
||||
// Avoid receiving two shutudown notifications.
|
||||
return GL_NOTIFICATION_CONTINUE;
|
||||
}
|
||||
|
||||
DBG_ASSERT(m_pApplicationManager);
|
||||
// we should let application manager to shutdown all allication
|
||||
// and dereference it as some requests may still reference to application manager
|
||||
m_pApplicationManager->ShutDown();
|
||||
m_pApplicationManager = NULL;
|
||||
|
||||
// Return processing to the pipeline.
|
||||
return GL_NOTIFICATION_CONTINUE;
|
||||
}
|
||||
|
|
@ -34,14 +38,16 @@ ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening(
|
|||
// Recycled the corresponding core app if its configuration changed
|
||||
//
|
||||
GLOBAL_NOTIFICATION_STATUS
|
||||
ASPNET_CORE_GLOBAL_MODULE::OnGlobalApplicationStop(
|
||||
_In_ IHttpApplicationStopProvider * pProvider
|
||||
ASPNET_CORE_GLOBAL_MODULE::OnGlobalConfigurationChange(
|
||||
_In_ IGlobalConfigurationChangeProvider * pProvider
|
||||
)
|
||||
{
|
||||
if (g_fInShutdown)
|
||||
{
|
||||
return GL_NOTIFICATION_CONTINUE;
|
||||
}
|
||||
// Retrieve the path that has changed.
|
||||
IHttpApplication* pApplication = pProvider->GetApplication();
|
||||
|
||||
PCWSTR pwszChangePath = pApplication->GetAppConfigPath();
|
||||
PCWSTR pwszChangePath = pProvider->GetChangePath();
|
||||
|
||||
// Test for an error.
|
||||
if (NULL != pwszChangePath &&
|
||||
|
|
@ -50,7 +56,7 @@ ASPNET_CORE_GLOBAL_MODULE::OnGlobalApplicationStop(
|
|||
{
|
||||
if (m_pApplicationManager != NULL)
|
||||
{
|
||||
m_pApplicationManager->RecycleApplication(pwszChangePath);
|
||||
m_pApplicationManager->RecycleApplicationFromManager(pwszChangePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ HRESULT_FROM_GETLASTERROR()
|
|||
extern PVOID g_pModuleId;
|
||||
extern BOOL g_fAspnetcoreRHAssemblyLoaded;
|
||||
extern BOOL g_fAspnetcoreRHLoadedError;
|
||||
extern BOOL g_fInShutdown;
|
||||
extern BOOL g_fEnableReferenceCountTracing;
|
||||
extern DWORD g_dwActiveServerProcesses;
|
||||
extern HINSTANCE g_hModule;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
|
|||
APPLICATION* pApplication = NULL;
|
||||
STACK_STRU(struFileName, 256);
|
||||
|
||||
hr = ASPNETCORE_CONFIG::GetConfig(g_pHttpServer, g_pModuleId, pHttpContext, &pConfig);
|
||||
hr = ASPNETCORE_CONFIG::GetConfig(g_pHttpServer, g_pModuleId, pHttpContext, g_hEventLog, &pConfig);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto Finished;
|
||||
|
|
@ -97,7 +97,7 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
|
|||
goto Finished;
|
||||
}
|
||||
|
||||
hr = pApplicationManager->GetApplicationInfo(
|
||||
hr = pApplicationManager->GetOrCreateApplicationInfo(
|
||||
g_pHttpServer,
|
||||
pConfig,
|
||||
&m_pApplicationInfo);
|
||||
|
|
@ -149,7 +149,8 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
|
|||
|
||||
// make sure application is in running state
|
||||
// cannot recreate the application as we cannot reload clr for inprocess
|
||||
if (pApplication->QueryStatus() != APPLICATION_STATUS::RUNNING)
|
||||
if (pApplication->QueryStatus() != APPLICATION_STATUS::RUNNING &&
|
||||
pApplication->QueryStatus() != APPLICATION_STATUS::STARTING)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED);
|
||||
goto Finished;
|
||||
|
|
@ -173,6 +174,7 @@ Finished:
|
|||
pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, hr);
|
||||
retVal = RQ_NOTIFICATION_FINISH_REQUEST;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
@ -189,4 +191,4 @@ ASPNET_CORE_PROXY_MODULE::OnAsyncCompletion(
|
|||
return m_pHandler->OnAsyncCompletion(
|
||||
pCompletionInfo->GetCompletionBytes(),
|
||||
pCompletionInfo->GetCompletionStatus());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,4 +47,4 @@ const
|
|||
{
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@
|
|||
enum APPLICATION_STATUS
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
STARTING,
|
||||
RUNNING,
|
||||
FAUL
|
||||
SHUTDOWN,
|
||||
FAIL
|
||||
};
|
||||
|
||||
class ASPNETCORE_CONFIG;
|
||||
|
|
@ -23,6 +25,10 @@ public:
|
|||
VOID
|
||||
ShutDown() = 0;
|
||||
|
||||
virtual
|
||||
VOID
|
||||
Recycle() = 0;
|
||||
|
||||
virtual
|
||||
~APPLICATION();
|
||||
|
||||
|
|
@ -42,7 +48,7 @@ public:
|
|||
|
||||
protected:
|
||||
mutable LONG m_cRefs;
|
||||
APPLICATION_STATUS m_status;
|
||||
volatile APPLICATION_STATUS m_status;
|
||||
IHttpServer* m_pHttpServer;
|
||||
ASPNETCORE_CONFIG* m_pConfig;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -158,6 +158,26 @@ Language=English
|
|||
%1
|
||||
.
|
||||
|
||||
Messageid=1022
|
||||
SymbolicName=ASPNETCORE_EVENT_RECYCLE_APP_FAILURE
|
||||
Language=English
|
||||
%1
|
||||
.
|
||||
|
||||
Messageid=1023
|
||||
SymbolicName=ASPNETCORE_EVENT_APP_IN_SHUTDOWN
|
||||
Language=English
|
||||
%1
|
||||
.
|
||||
|
||||
Messageid=1024
|
||||
SymbolicName=ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED
|
||||
Language=English
|
||||
%1
|
||||
.
|
||||
|
||||
|
||||
|
||||
;
|
||||
;#endif // _ASPNETCORE_MODULE_MSG_H_
|
||||
;
|
||||
|
|
|
|||
|
|
@ -46,12 +46,16 @@ ASPNETCORE_CONFIG::GetConfig(
|
|||
_In_ IHttpServer *pHttpServer,
|
||||
_In_ HTTP_MODULE_ID pModuleId,
|
||||
_In_ IHttpContext *pHttpContext,
|
||||
_In_ HANDLE hEventLog,
|
||||
_Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig
|
||||
)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
IHttpApplication *pHttpApplication = pHttpContext->GetApplication();
|
||||
ASPNETCORE_CONFIG *pAspNetCoreConfig = NULL;
|
||||
STRU struHostFxrDllLocation;
|
||||
PWSTR* pwzArgv;
|
||||
DWORD dwArgCount;
|
||||
|
||||
if (ppAspNetCoreConfig == NULL)
|
||||
{
|
||||
|
|
@ -85,6 +89,29 @@ ASPNETCORE_CONFIG::GetConfig(
|
|||
goto Finished;
|
||||
}
|
||||
|
||||
// Modify config for inprocess.
|
||||
if (pAspNetCoreConfig->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS)
|
||||
{
|
||||
if (FAILED(hr = HOSTFXR_UTILITY::GetHostFxrParameters(
|
||||
hEventLog,
|
||||
pAspNetCoreConfig->QueryProcessPath()->QueryStr(),
|
||||
pAspNetCoreConfig->QueryApplicationPhysicalPath()->QueryStr(),
|
||||
pAspNetCoreConfig->QueryArguments()->QueryStr(),
|
||||
&struHostFxrDllLocation,
|
||||
&dwArgCount,
|
||||
&pwzArgv)))
|
||||
{
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
if (FAILED(hr = pAspNetCoreConfig->SetHostFxrFullPath(struHostFxrDllLocation.QueryStr())))
|
||||
{
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
pAspNetCoreConfig->SetHostFxrArguments(dwArgCount, pwzArgv);
|
||||
}
|
||||
|
||||
hr = pHttpApplication->GetModuleContextContainer()->
|
||||
SetModuleContext(pAspNetCoreConfig, pModuleId);
|
||||
if (FAILED(hr))
|
||||
|
|
@ -94,8 +121,8 @@ ASPNETCORE_CONFIG::GetConfig(
|
|||
delete pAspNetCoreConfig;
|
||||
|
||||
pAspNetCoreConfig = (ASPNETCORE_CONFIG*)pHttpApplication->
|
||||
GetModuleContextContainer()->
|
||||
GetModuleContext(pModuleId);
|
||||
GetModuleContextContainer()->
|
||||
GetModuleContext(pModuleId);
|
||||
|
||||
_ASSERT(pAspNetCoreConfig != NULL);
|
||||
|
||||
|
|
@ -539,7 +566,7 @@ Finished:
|
|||
pWindowsAuthenticationElement = NULL;
|
||||
}
|
||||
|
||||
if (pAnonymousAuthenticationElement!= NULL)
|
||||
if (pAnonymousAuthenticationElement != NULL)
|
||||
{
|
||||
pAnonymousAuthenticationElement->Release();
|
||||
pAnonymousAuthenticationElement = NULL;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ public:
|
|||
_In_ IHttpServer *pHttpServer,
|
||||
_In_ HTTP_MODULE_ID pModuleId,
|
||||
_In_ IHttpContext *pHttpContext,
|
||||
_In_ HANDLE hEventLog,
|
||||
_Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,8 +28,11 @@
|
|||
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDERR_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x. First 4KB characters of captured stderr logs on startup:\r\n%s"
|
||||
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x. Last 4KB characters of captured stdout and stderr logs:\r\n%s"
|
||||
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x. Please check the stderr logs for more information."
|
||||
#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG L"Application '%s' is recycled due to app_offline file was detected."
|
||||
#define ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG L"Application '%s' is recycled due to configuration changed"
|
||||
#define ASPNETCORE_EVENT_APP_IN_SHUTDOWN_MSG L"Application shutting down."
|
||||
#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG L"Application '%s' was recycled after detecting the app_offline file."
|
||||
#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED_MSG L"App_offline.htm has been removed from the application. Application will be recycled."
|
||||
#define ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG L"Application '%s' was recycled due to configuration change"
|
||||
#define ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG L"Failed to recycle application due to a configuration change at '%s'. Recycling worker process."
|
||||
#define ASPNETCORE_EVENT_MODULE_DISABLED_MSG L"AspNetCore Module is disabled"
|
||||
#define ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP_MSG L"Application '%s' was compiled for .NET Framework. Please compile for .NET core to run the inprocess application or change the process mode to out of process. ErrorCode = '0x%x'."
|
||||
#define ASPNETCORE_EVENT_PORTABLE_APP_DOTNET_MISSING_MSG L"Could not find dotnet.exe on the system PATH environment variable for portable application '%s'. Check that a valid path to dotnet is on the PATH and the bitness of dotnet matches the bitness of the IIS worker process. ErrorCode = '0x%x'."
|
||||
|
|
|
|||
|
|
@ -28,4 +28,4 @@
|
|||
<PackageReference Include="Microsoft.Extensions.SecurityHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsSecurityHelperSourcesPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
@ -59,6 +59,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
[DllImport(AspNetCoreModuleDll)]
|
||||
internal unsafe static extern HttpApiTypes.HTTP_REQUEST_V2* http_get_raw_request(IntPtr pInProcessHandler);
|
||||
|
||||
[DllImport(AspNetCoreModuleDll)]
|
||||
internal unsafe static extern void http_stop_calls_into_managed();
|
||||
|
||||
[DllImport(AspNetCoreModuleDll)]
|
||||
internal unsafe static extern void http_stop_incoming_requests();
|
||||
|
||||
[DllImport(AspNetCoreModuleDll)]
|
||||
internal unsafe static extern HttpApiTypes.HTTP_RESPONSE_V2* http_get_raw_response(IntPtr pInProcessHandler);
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
|
||||
protected Exception _applicationException;
|
||||
private readonly MemoryPool<byte> _memoryPool;
|
||||
private readonly IISHttpServer _server;
|
||||
|
||||
private GCHandle _thisHandle;
|
||||
private MemoryHandle _inputHandle;
|
||||
|
|
@ -59,13 +60,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
private const string NegotiateString = "Negotiate";
|
||||
private const string BasicString = "Basic";
|
||||
|
||||
internal unsafe IISHttpContext(MemoryPool<byte> memoryPool, IntPtr pInProcessHandler, IISOptions options)
|
||||
internal unsafe IISHttpContext(MemoryPool<byte> memoryPool, IntPtr pInProcessHandler, IISOptions options, IISHttpServer server)
|
||||
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.http_get_raw_request(pInProcessHandler))
|
||||
{
|
||||
_thisHandle = GCHandle.Alloc(this);
|
||||
|
||||
_memoryPool = memoryPool;
|
||||
_pInProcessHandler = pInProcessHandler;
|
||||
_server = server;
|
||||
|
||||
NativeMethods.http_set_managed_context(pInProcessHandler, (IntPtr)_thisHandle);
|
||||
unsafe
|
||||
|
|
@ -199,6 +201,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
_reasonPhrase = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal IISHttpServer Server
|
||||
{
|
||||
get { return _server; }
|
||||
}
|
||||
|
||||
private async Task InitializeResponseAwaited()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
{
|
||||
private readonly IHttpApplication<TContext> _application;
|
||||
|
||||
public IISHttpContextOfT(MemoryPool<byte> memoryPool, IHttpApplication<TContext> application, IntPtr pInProcessHandler, IISOptions options)
|
||||
: base(memoryPool, pInProcessHandler, options)
|
||||
public IISHttpContextOfT(MemoryPool<byte> memoryPool, IHttpApplication<TContext> application, IntPtr pInProcessHandler, IISOptions options, IISHttpServer server)
|
||||
: base(memoryPool, pInProcessHandler, options, server)
|
||||
{
|
||||
_application = application;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
private readonly IAuthenticationSchemeProvider _authentication;
|
||||
private readonly IISOptions _options;
|
||||
|
||||
private volatile int _stopping;
|
||||
private bool Stopping => _stopping == 1;
|
||||
private int _outstandingRequests;
|
||||
private readonly TaskCompletionSource<object> _shutdownSignal = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
public IFeatureCollection Features { get; } = new FeatureCollection();
|
||||
public IISHttpServer(IApplicationLifetime applicationLifetime, IAuthenticationSchemeProvider authentication, IOptions<IISOptions> options)
|
||||
{
|
||||
|
|
@ -45,7 +50,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
{
|
||||
_httpServerHandle = GCHandle.Alloc(this);
|
||||
|
||||
_iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options);
|
||||
_iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options, this);
|
||||
|
||||
// Start the server by registering the callback
|
||||
NativeMethods.register_callbacks(_requestHandler, _shutdownHandler, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
|
||||
|
|
@ -55,15 +60,54 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Drain pending requests
|
||||
void RegisterCancelation()
|
||||
{
|
||||
cancellationToken.Register(() =>
|
||||
{
|
||||
NativeMethods.http_stop_calls_into_managed();
|
||||
_shutdownSignal.TrySetResult(null);
|
||||
});
|
||||
}
|
||||
if (Interlocked.Exchange(ref _stopping, 1) == 1)
|
||||
{
|
||||
RegisterCancelation();
|
||||
|
||||
// Stop all further calls back into managed code by unhooking the callback
|
||||
return _shutdownSignal.Task;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
// First call back into native saying "DON'T SEND ME ANY MORE REQUESTS"
|
||||
NativeMethods.http_stop_incoming_requests();
|
||||
|
||||
try
|
||||
{
|
||||
// Wait for active requests to drain
|
||||
if (_outstandingRequests > 0)
|
||||
{
|
||||
RegisterCancelation();
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have drained all requests. Block any callbacks into managed at this point.
|
||||
NativeMethods.http_stop_calls_into_managed();
|
||||
_shutdownSignal.TrySetResult(null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_shutdownSignal.TrySetException(ex);
|
||||
}
|
||||
|
||||
return _shutdownSignal.Task;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stopping = 1;
|
||||
|
||||
// Block any more calls into managed from native as we are unloading.
|
||||
NativeMethods.http_stop_calls_into_managed();
|
||||
_shutdownSignal.TrySetResult(null);
|
||||
|
||||
if (_httpServerHandle.IsAllocated)
|
||||
{
|
||||
_httpServerHandle.Free();
|
||||
|
|
@ -76,6 +120,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
{
|
||||
// Unwrap the server so we can create an http context and process the request
|
||||
var server = (IISHttpServer)GCHandle.FromIntPtr(pvRequestContext).Target;
|
||||
Interlocked.Increment(ref server._outstandingRequests);
|
||||
|
||||
var context = server._iisContextFactory.CreateHttpContext(pInProcessHandler);
|
||||
|
||||
|
|
@ -105,6 +150,13 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
// Post completion after completing the request to resume the state machine
|
||||
context.PostCompletion(ConvertRequestCompletionResults(completedTask.Result));
|
||||
|
||||
if (Interlocked.Decrement(ref context.Server._outstandingRequests) == 0 && context.Server.Stopping)
|
||||
{
|
||||
// All requests have been drained.
|
||||
NativeMethods.http_stop_calls_into_managed();
|
||||
context.Server._shutdownSignal.TrySetResult(null);
|
||||
}
|
||||
|
||||
// Dispose the context
|
||||
context.Dispose();
|
||||
}
|
||||
|
|
@ -120,19 +172,27 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
private readonly IHttpApplication<T> _application;
|
||||
private readonly MemoryPool<byte> _memoryPool;
|
||||
private readonly IISOptions _options;
|
||||
private readonly IISHttpServer _server;
|
||||
|
||||
public IISContextFactory(MemoryPool<byte> memoryPool, IHttpApplication<T> application, IISOptions options)
|
||||
public IISContextFactory(MemoryPool<byte> memoryPool, IHttpApplication<T> application, IISOptions options, IISHttpServer server)
|
||||
{
|
||||
_application = application;
|
||||
_memoryPool = memoryPool;
|
||||
_options = options;
|
||||
_server = server;
|
||||
}
|
||||
|
||||
public IISHttpContext CreateHttpContext(IntPtr pInProcessHandler)
|
||||
{
|
||||
return new IISHttpContextOfT<T>(_memoryPool, _application, pInProcessHandler, _options);
|
||||
return new IISHttpContextOfT<T>(_memoryPool, _application, pInProcessHandler, _options, _server);
|
||||
}
|
||||
}
|
||||
|
||||
~IISHttpServer()
|
||||
{
|
||||
// If this finalize is invoked, try our best to block all calls into managed.
|
||||
NativeMethods.http_stop_calls_into_managed();
|
||||
}
|
||||
}
|
||||
|
||||
// Over engineering to avoid allocations...
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
if (_flushTcs == null || _flushTcs.Task.IsCompleted)
|
||||
{
|
||||
_flushTcs = new TaskCompletionSource<object>();
|
||||
|
||||
awaitable.OnCompleted(_flushCompleted);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
|
|||
ASPNETCORE_CONFIG* pConfig) :
|
||||
APPLICATION(pHttpServer, pConfig),
|
||||
m_ProcessExitCode(0),
|
||||
m_fManagedAppLoaded(FALSE),
|
||||
m_fLoadManagedAppError(FALSE),
|
||||
m_fInitialized(FALSE),
|
||||
m_fRecycleProcessCalled(FALSE),
|
||||
m_hLogFileHandle(INVALID_HANDLE_VALUE),
|
||||
m_hErrReadPipe(INVALID_HANDLE_VALUE),
|
||||
m_hErrWritePipe(INVALID_HANDLE_VALUE),
|
||||
m_dwStdErrReadTotal(0),
|
||||
m_fDoneStdRedirect(FALSE),
|
||||
m_dwStdErrReadTotal(0)
|
||||
m_fBlockCallbacksIntoManaged(FALSE),
|
||||
m_fInitialized(FALSE),
|
||||
m_fShutdownCalledFromNative(FALSE),
|
||||
m_fShutdownCalledFromManaged(FALSE)
|
||||
{
|
||||
// is it guaranteed that we have already checked app offline at this point?
|
||||
// If so, I don't think there is much to do here.
|
||||
|
|
@ -24,111 +24,158 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
|
|||
InitializeSRWLock(&m_srwLock);
|
||||
|
||||
// TODO we can probably initialized as I believe we are the only ones calling recycle.
|
||||
m_fInitialized = TRUE;
|
||||
m_status = APPLICATION_STATUS::RUNNING;
|
||||
m_status = APPLICATION_STATUS::STARTING;
|
||||
}
|
||||
|
||||
IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
|
||||
{
|
||||
Recycle();
|
||||
// TODO check if anything else needs to be cleaned up
|
||||
}
|
||||
|
||||
__override
|
||||
VOID
|
||||
IN_PROCESS_APPLICATION::ShutDown()
|
||||
{
|
||||
//todo
|
||||
}
|
||||
DWORD dwThreadStatus = 0;
|
||||
DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS();
|
||||
HANDLE handle = NULL;
|
||||
WIN32_FIND_DATA fileData;
|
||||
BOOL fLocked = FALSE;
|
||||
|
||||
// This is the same function as before, TODO configrm if we need to change anything for configuration.
|
||||
VOID
|
||||
IN_PROCESS_APPLICATION::Recycle(
|
||||
VOID
|
||||
)
|
||||
{
|
||||
if (m_fInitialized)
|
||||
if (IsDebuggerPresent())
|
||||
{
|
||||
DWORD dwThreadStatus = 0;
|
||||
DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS();
|
||||
HANDLE handle = NULL;
|
||||
WIN32_FIND_DATA fileData;
|
||||
dwTimeout = INFINITE;
|
||||
}
|
||||
else
|
||||
{
|
||||
dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS();
|
||||
}
|
||||
|
||||
AcquireSRWLockExclusive(&m_srwLock);
|
||||
if (m_fShutdownCalledFromNative ||
|
||||
m_status == APPLICATION_STATUS::STARTING ||
|
||||
m_status == APPLICATION_STATUS::FAIL)
|
||||
{
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
if (!m_pHttpServer->IsCommandLineLaunch() &&
|
||||
!m_fRecycleProcessCalled &&
|
||||
(m_pHttpServer->GetAdminManager() != NULL))
|
||||
{
|
||||
// IIS scenario.
|
||||
// notify IIS first so that new request will be routed to new worker process
|
||||
m_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand");
|
||||
}
|
||||
AcquireSRWLockExclusive(&m_srwLock);
|
||||
fLocked = TRUE;
|
||||
|
||||
m_fRecycleProcessCalled = TRUE;
|
||||
if (m_fShutdownCalledFromNative ||
|
||||
m_status == APPLICATION_STATUS::STARTING ||
|
||||
m_status == APPLICATION_STATUS::FAIL)
|
||||
{
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
// First call into the managed server and shutdown
|
||||
if (m_ShutdownHandler != NULL)
|
||||
{
|
||||
m_ShutdownHandler(m_ShutdownHandlerContext);
|
||||
m_ShutdownHandler = NULL;
|
||||
}
|
||||
// We need to keep track of when both managed and native initiate shutdown
|
||||
// to avoid AVs. If shutdown has already been initiated in managed, we don't want to call into
|
||||
// managed. We still need to wait on main exiting no matter what. m_fShutdownCalledFromNative
|
||||
// is used for detecting redundant calls and blocking more requests to OnExecuteRequestHandler.
|
||||
m_fShutdownCalledFromNative = TRUE;
|
||||
m_status = APPLICATION_STATUS::SHUTDOWN;
|
||||
|
||||
if (!m_fShutdownCalledFromManaged)
|
||||
{
|
||||
// Initiate a recycle such that another worker process is created to replace this one.
|
||||
|
||||
m_ShutdownHandler(m_ShutdownHandlerContext);
|
||||
m_ShutdownHandler = NULL;
|
||||
|
||||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
fLocked = FALSE;
|
||||
|
||||
// Release the lock before we wait on the thread to exit.
|
||||
if (m_hThread != NULL &&
|
||||
GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 &&
|
||||
dwThreadStatus == STILL_ACTIVE)
|
||||
{
|
||||
// wait for gracefullshut down, i.e., the exit of the background thread or timeout
|
||||
// wait for graceful shutdown, i.e., the exit of the background thread or timeout
|
||||
if (WaitForSingleObject(m_hThread, dwTimeout) != WAIT_OBJECT_0)
|
||||
{
|
||||
// if the thread is still running, we need kill it first before exit to avoid AV
|
||||
if (GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE)
|
||||
{
|
||||
// Calling back into managed at this point is prone to have AVs
|
||||
// Calling terminate thread here may be our best solution.
|
||||
TerminateThread(m_hThread, STATUS_CONTROL_C_EXIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(m_hThread);
|
||||
m_hThread = NULL;
|
||||
s_Application = NULL;
|
||||
|
||||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
|
||||
CloseStdErrHandles();
|
||||
|
||||
if (m_pStdFile != NULL)
|
||||
{
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
fclose(m_pStdFile);
|
||||
}
|
||||
|
||||
if (m_hLogFileHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_Timer.CancelTimer();
|
||||
CloseHandle(m_hLogFileHandle);
|
||||
m_hLogFileHandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
// delete empty log file
|
||||
handle = FindFirstFile(m_struLogFilePath.QueryStr(), &fileData);
|
||||
if (handle != INVALID_HANDLE_VALUE &&
|
||||
fileData.nFileSizeHigh == 0 &&
|
||||
fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh
|
||||
{
|
||||
FindClose(handle);
|
||||
// no need to check whether the deletion succeeds
|
||||
// as nothing can be done
|
||||
DeleteFile(m_struLogFilePath.QueryStr());
|
||||
}
|
||||
|
||||
if (m_pHttpServer && m_pHttpServer->IsCommandLineLaunch())
|
||||
{
|
||||
// IISExpress scenario
|
||||
// Can only call exit to terminate current process
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(m_hThread);
|
||||
m_hThread = NULL;
|
||||
s_Application = NULL;
|
||||
|
||||
CloseStdErrHandles();
|
||||
|
||||
if (m_pStdFile != NULL)
|
||||
{
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
fclose(m_pStdFile);
|
||||
}
|
||||
|
||||
if (m_hLogFileHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_Timer.CancelTimer();
|
||||
CloseHandle(m_hLogFileHandle);
|
||||
m_hLogFileHandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
// delete empty log file
|
||||
handle = FindFirstFile(m_struLogFilePath.QueryStr(), &fileData);
|
||||
if (handle != INVALID_HANDLE_VALUE &&
|
||||
fileData.nFileSizeHigh == 0 &&
|
||||
fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh
|
||||
{
|
||||
FindClose(handle);
|
||||
// no need to check whether the deletion succeeds
|
||||
// as nothing can be done
|
||||
DeleteFile(m_struLogFilePath.QueryStr());
|
||||
}
|
||||
|
||||
Finished:
|
||||
|
||||
if (fLocked)
|
||||
{
|
||||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
VOID
|
||||
IN_PROCESS_APPLICATION::Recycle(
|
||||
VOID
|
||||
)
|
||||
{
|
||||
// We need to guarantee that recycle is only called once, as calling pHttpServer->RecycleProcess
|
||||
// multiple times can lead to AVs.
|
||||
if (m_fRecycleCalled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
AcquireSRWLockExclusive(&m_srwLock);
|
||||
if (m_fRecycleCalled)
|
||||
{
|
||||
goto Finished;
|
||||
}
|
||||
if (!m_pHttpServer->IsCommandLineLaunch())
|
||||
{
|
||||
// IIS scenario.
|
||||
// notify IIS first so that new request will be routed to new worker process
|
||||
m_pHttpServer->RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand");
|
||||
}
|
||||
else
|
||||
{
|
||||
// IISExpress scenario
|
||||
// Can only call exit to terminate current process
|
||||
ShutDown();
|
||||
exit(0);
|
||||
}
|
||||
Finished:
|
||||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
}
|
||||
|
||||
REQUEST_NOTIFICATION_STATUS
|
||||
|
|
@ -140,18 +187,33 @@ IN_PROCESS_APPLICATION::OnAsyncCompletion(
|
|||
{
|
||||
REQUEST_NOTIFICATION_STATUS dwRequestNotificationStatus = RQ_NOTIFICATION_CONTINUE;
|
||||
|
||||
ReferenceApplication();
|
||||
|
||||
if (pInProcessHandler->QueryIsManagedRequestComplete())
|
||||
{
|
||||
// means PostCompletion has been called and this is the associated callback.
|
||||
dwRequestNotificationStatus = pInProcessHandler->QueryAsyncCompletionStatus();
|
||||
// TODO cleanup whatever disconnect listener there is
|
||||
return dwRequestNotificationStatus;
|
||||
}
|
||||
else if (m_fBlockCallbacksIntoManaged)
|
||||
{
|
||||
// this can potentially happen in ungraceful shutdown.
|
||||
// Or something really wrong happening with async completions
|
||||
// At this point, managed is in a shutting down state and we cannot send a request to it.
|
||||
pInProcessHandler->QueryHttpContext()->GetResponse()->SetStatus(503,
|
||||
"Server has been shutdown",
|
||||
0,
|
||||
(ULONG)HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS));
|
||||
dwRequestNotificationStatus = RQ_NOTIFICATION_FINISH_REQUEST;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Call the managed handler for async completion.
|
||||
return m_AsyncCompletionHandler(pInProcessHandler->QueryManagedHttpContext(), hrCompletionStatus, cbCompletion);
|
||||
dwRequestNotificationStatus = m_AsyncCompletionHandler(pInProcessHandler->QueryManagedHttpContext(), hrCompletionStatus, cbCompletion);
|
||||
}
|
||||
|
||||
DereferenceApplication();
|
||||
|
||||
return dwRequestNotificationStatus;
|
||||
}
|
||||
|
||||
REQUEST_NOTIFICATION_STATUS
|
||||
|
|
@ -160,27 +222,47 @@ IN_PROCESS_APPLICATION::OnExecuteRequest(
|
|||
_In_ IN_PROCESS_HANDLER* pInProcessHandler
|
||||
)
|
||||
{
|
||||
if (m_RequestHandler != NULL)
|
||||
{
|
||||
return m_RequestHandler(pInProcessHandler, m_RequestHandlerContext);
|
||||
}
|
||||
REQUEST_NOTIFICATION_STATUS dwRequestNotificationStatus = RQ_NOTIFICATION_CONTINUE;
|
||||
PFN_REQUEST_HANDLER pRequestHandler = NULL;
|
||||
|
||||
//
|
||||
// return error as the application did not register callback
|
||||
//
|
||||
if (ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::IsEnabled(pHttpContext->GetTraceContext()))
|
||||
ReferenceApplication();
|
||||
pRequestHandler = m_RequestHandler;
|
||||
|
||||
if (pRequestHandler == NULL)
|
||||
{
|
||||
ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::RaiseEvent(pHttpContext->GetTraceContext(),
|
||||
NULL,
|
||||
//
|
||||
// return error as the application did not register callback
|
||||
//
|
||||
if (ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::IsEnabled(pHttpContext->GetTraceContext()))
|
||||
{
|
||||
ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::RaiseEvent(pHttpContext->GetTraceContext(),
|
||||
NULL,
|
||||
(ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE);
|
||||
}
|
||||
|
||||
pHttpContext->GetResponse()->SetStatus(500,
|
||||
"Internal Server Error",
|
||||
0,
|
||||
(ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE);
|
||||
|
||||
dwRequestNotificationStatus = RQ_NOTIFICATION_FINISH_REQUEST;
|
||||
}
|
||||
else if (m_status != APPLICATION_STATUS::RUNNING || m_fBlockCallbacksIntoManaged)
|
||||
{
|
||||
pHttpContext->GetResponse()->SetStatus(503,
|
||||
"Server is currently shutting down.",
|
||||
0,
|
||||
(ULONG)HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS));
|
||||
dwRequestNotificationStatus = RQ_NOTIFICATION_FINISH_REQUEST;
|
||||
}
|
||||
else
|
||||
{
|
||||
dwRequestNotificationStatus = pRequestHandler(pInProcessHandler, m_RequestHandlerContext);
|
||||
}
|
||||
|
||||
pHttpContext->GetResponse()->SetStatus(500,
|
||||
"Internal Server Error",
|
||||
0,
|
||||
(ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE);
|
||||
DereferenceApplication();
|
||||
|
||||
return RQ_NOTIFICATION_FINISH_REQUEST;
|
||||
return dwRequestNotificationStatus;
|
||||
}
|
||||
|
||||
VOID
|
||||
|
|
@ -203,7 +285,7 @@ IN_PROCESS_APPLICATION::SetCallbackHandles(
|
|||
SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE);
|
||||
// Initialization complete
|
||||
SetEvent(m_pInitalizeEvent);
|
||||
|
||||
m_fInitialized = TRUE;
|
||||
}
|
||||
|
||||
VOID
|
||||
|
|
@ -478,14 +560,20 @@ IN_PROCESS_APPLICATION::LoadManagedApplication
|
|||
DWORD dwResult;
|
||||
BOOL fLocked = FALSE;
|
||||
|
||||
if (m_fManagedAppLoaded || m_fLoadManagedAppError)
|
||||
ReferenceApplication();
|
||||
|
||||
if (m_status != APPLICATION_STATUS::STARTING)
|
||||
{
|
||||
// Core CLR has already been loaded.
|
||||
// Cannot load more than once even there was a failure
|
||||
if (m_fLoadManagedAppError)
|
||||
if (m_status == APPLICATION_STATUS::FAIL)
|
||||
{
|
||||
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
|
||||
}
|
||||
else if (m_status == APPLICATION_STATUS::SHUTDOWN)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IS_SCHEDULED);
|
||||
}
|
||||
|
||||
goto Finished;
|
||||
}
|
||||
|
|
@ -495,12 +583,16 @@ IN_PROCESS_APPLICATION::LoadManagedApplication
|
|||
|
||||
AcquireSRWLockExclusive(&m_srwLock);
|
||||
fLocked = TRUE;
|
||||
if (m_fManagedAppLoaded || m_fLoadManagedAppError)
|
||||
if (m_status != APPLICATION_STATUS::STARTING)
|
||||
{
|
||||
if (m_fLoadManagedAppError)
|
||||
if (m_status == APPLICATION_STATUS::FAIL)
|
||||
{
|
||||
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
|
||||
}
|
||||
else if (m_status == APPLICATION_STATUS::SHUTDOWN)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IS_SCHEDULED);
|
||||
}
|
||||
|
||||
goto Finished;
|
||||
}
|
||||
|
|
@ -566,16 +658,14 @@ IN_PROCESS_APPLICATION::LoadManagedApplication
|
|||
goto Finished;
|
||||
}
|
||||
|
||||
m_fManagedAppLoaded = TRUE;
|
||||
m_status = APPLICATION_STATUS::RUNNING;
|
||||
|
||||
Finished:
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
STACK_STRU(strEventMsg, 256);
|
||||
// Question: in case of application loading failure, should we allow retry on
|
||||
// following request or block the activation at all
|
||||
m_fLoadManagedAppError = TRUE; // m_hThread != NULL ?
|
||||
m_status = APPLICATION_STATUS::FAIL;
|
||||
|
||||
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
|
||||
|
|
@ -595,6 +685,8 @@ Finished:
|
|||
ReleaseSRWLockExclusive(&m_srwLock);
|
||||
}
|
||||
|
||||
DereferenceApplication();
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
|
|
@ -657,7 +749,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication(
|
|||
hr = ERROR_BAD_ENVIRONMENT;
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
|
||||
// Get the entry point for main
|
||||
pProc = (hostfxr_main_fn)GetProcAddress(hModule, "hostfxr_main");
|
||||
if (pProc == NULL)
|
||||
|
|
@ -683,90 +775,100 @@ IN_PROCESS_APPLICATION::ExecuteApplication(
|
|||
|
||||
Finished:
|
||||
|
||||
if (!m_fRecycleProcessCalled)
|
||||
//
|
||||
// this method is called by the background thread and should never exit unless shutdown
|
||||
// If main returned and shutdown was not called in managed, we want to block native from calling into
|
||||
// managed. To do this, we can say that shutdown was called from managed.
|
||||
// Don't bother locking here as there will always be a race between receiving a native shutdown
|
||||
// notification and unexpected managed exit.
|
||||
//
|
||||
m_status = APPLICATION_STATUS::SHUTDOWN;
|
||||
m_fShutdownCalledFromManaged = TRUE;
|
||||
FreeLibrary(hModule);
|
||||
|
||||
if (!m_fShutdownCalledFromNative)
|
||||
{
|
||||
//
|
||||
// Ungraceful shutdown, try to log an error message.
|
||||
// This will be a common place for errors as it means the hostfxr_main returned
|
||||
// or there was an exception.
|
||||
//
|
||||
|
||||
CHAR pzFileContents[4096] = { 0 };
|
||||
DWORD dwNumBytesRead;
|
||||
STRU struStdErrLog;
|
||||
LARGE_INTEGER li = { 0 };
|
||||
STRU strEventMsg;
|
||||
BOOL fLogged = FALSE;
|
||||
DWORD dwFilePointer = 0;
|
||||
|
||||
if (m_pConfig->QueryStdoutLogEnabled())
|
||||
LogErrorsOnMainExit(hr);
|
||||
if (m_fInitialized)
|
||||
{
|
||||
// Put stdout/stderr logs into
|
||||
if (m_hLogFileHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (GetFileSizeEx(m_hLogFileHandle, &li) && li.LowPart > 0 && li.HighPart == 0)
|
||||
{
|
||||
if (li.LowPart > 4096)
|
||||
{
|
||||
dwFilePointer = SetFilePointer(m_hLogFileHandle, -4096, NULL, FILE_END);
|
||||
}
|
||||
else
|
||||
{
|
||||
dwFilePointer = SetFilePointer(m_hLogFileHandle, 0, NULL, FILE_BEGIN);
|
||||
}
|
||||
if (dwFilePointer != INVALID_SET_FILE_POINTER)
|
||||
{
|
||||
if (ReadFile(m_hLogFileHandle, pzFileContents, 4096, &dwNumBytesRead, NULL))
|
||||
{
|
||||
if (SUCCEEDED(struStdErrLog.CopyA(m_pzFileContents, m_dwStdErrReadTotal)) &&
|
||||
SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG,
|
||||
m_pConfig->QueryApplicationPath()->QueryStr(),
|
||||
m_pConfig->QueryApplicationPhysicalPath()->QueryStr(),
|
||||
hr,
|
||||
struStdErrLog.QueryStr())))
|
||||
{
|
||||
UTILITY::LogEvent(g_hEventLog,
|
||||
EVENTLOG_ERROR_TYPE,
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT,
|
||||
strEventMsg.QueryStr());
|
||||
fLogged = TRUE;
|
||||
//
|
||||
// If the inprocess server was initialized, we need to cause recycle to be called on the worker process.
|
||||
//
|
||||
Recycle();
|
||||
}
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
VOID
|
||||
IN_PROCESS_APPLICATION::LogErrorsOnMainExit(
|
||||
HRESULT hr
|
||||
)
|
||||
{
|
||||
//
|
||||
// Ungraceful shutdown, try to log an error message.
|
||||
// This will be a common place for errors as it means the hostfxr_main returned
|
||||
// or there was an exception.
|
||||
//
|
||||
CHAR pzFileContents[4096] = { 0 };
|
||||
DWORD dwNumBytesRead;
|
||||
STRU struStdErrLog;
|
||||
LARGE_INTEGER li = { 0 };
|
||||
STRU strEventMsg;
|
||||
BOOL fLogged = FALSE;
|
||||
DWORD dwFilePointer = 0;
|
||||
|
||||
if (m_pConfig->QueryStdoutLogEnabled())
|
||||
{
|
||||
// Put stdout/stderr logs into
|
||||
if (m_hLogFileHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (GetFileSizeEx(m_hLogFileHandle, &li) && li.LowPart > 0 && li.HighPart == 0)
|
||||
{
|
||||
if (li.LowPart > 4096)
|
||||
{
|
||||
dwFilePointer = SetFilePointer(m_hLogFileHandle, -4096, NULL, FILE_END);
|
||||
}
|
||||
else
|
||||
{
|
||||
dwFilePointer = SetFilePointer(m_hLogFileHandle, 0, NULL, FILE_BEGIN);
|
||||
}
|
||||
if (dwFilePointer != INVALID_SET_FILE_POINTER)
|
||||
{
|
||||
if (ReadFile(m_hLogFileHandle, pzFileContents, 4096, &dwNumBytesRead, NULL))
|
||||
{
|
||||
if (SUCCEEDED(struStdErrLog.CopyA(m_pzFileContents, m_dwStdErrReadTotal)) &&
|
||||
SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG,
|
||||
m_pConfig->QueryApplicationPath()->QueryStr(),
|
||||
m_pConfig->QueryApplicationPhysicalPath()->QueryStr(),
|
||||
hr,
|
||||
struStdErrLog.QueryStr())))
|
||||
{
|
||||
UTILITY::LogEvent(g_hEventLog,
|
||||
EVENTLOG_ERROR_TYPE,
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT,
|
||||
strEventMsg.QueryStr());
|
||||
fLogged = TRUE;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_dwStdErrReadTotal > 0)
|
||||
{
|
||||
if (m_dwStdErrReadTotal > 0)
|
||||
{
|
||||
if (SUCCEEDED(struStdErrLog.CopyA(m_pzFileContents, m_dwStdErrReadTotal)) &&
|
||||
SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDERR_MSG,
|
||||
m_pConfig->QueryApplicationPath()->QueryStr(),
|
||||
m_pConfig->QueryApplicationPhysicalPath()->QueryStr(),
|
||||
hr,
|
||||
struStdErrLog.QueryStr())))
|
||||
{
|
||||
UTILITY::LogEvent(g_hEventLog,
|
||||
EVENTLOG_ERROR_TYPE,
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT,
|
||||
strEventMsg.QueryStr());
|
||||
fLogged = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fLogged)
|
||||
{
|
||||
// If we didn't log, log the generic message.
|
||||
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG,
|
||||
m_pConfig->QueryApplicationPath()->QueryStr(),
|
||||
m_pConfig->QueryApplicationPhysicalPath()->QueryStr(),
|
||||
hr)))
|
||||
if (SUCCEEDED(struStdErrLog.CopyA(m_pzFileContents, m_dwStdErrReadTotal)) &&
|
||||
SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDERR_MSG,
|
||||
m_pConfig->QueryApplicationPath()->QueryStr(),
|
||||
m_pConfig->QueryApplicationPhysicalPath()->QueryStr(),
|
||||
hr,
|
||||
struStdErrLog.QueryStr())))
|
||||
{
|
||||
UTILITY::LogEvent(g_hEventLog,
|
||||
EVENTLOG_ERROR_TYPE,
|
||||
|
|
@ -775,11 +877,24 @@ Finished:
|
|||
fLogged = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// The destructor of inprocessapplication will call recycle. No need to recycle here.
|
||||
}
|
||||
|
||||
return hr;
|
||||
if (!fLogged)
|
||||
{
|
||||
// If we didn't log, log the generic message.
|
||||
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG,
|
||||
m_pConfig->QueryApplicationPath()->QueryStr(),
|
||||
m_pConfig->QueryApplicationPhysicalPath()->QueryStr(),
|
||||
hr)))
|
||||
{
|
||||
UTILITY::LogEvent(g_hEventLog,
|
||||
EVENTLOG_ERROR_TYPE,
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT,
|
||||
strEventMsg.QueryStr());
|
||||
fLogged = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -19,18 +19,19 @@ public:
|
|||
ShutDown();
|
||||
|
||||
VOID
|
||||
SetCallbackHandles(
|
||||
_In_ PFN_REQUEST_HANDLER request_callback,
|
||||
_In_ PFN_SHUTDOWN_HANDLER shutdown_callback,
|
||||
_In_ PFN_MANAGED_CONTEXT_HANDLER managed_context_callback,
|
||||
_In_ VOID* pvRequstHandlerContext,
|
||||
_In_ VOID* pvShutdownHandlerContext
|
||||
);
|
||||
SetCallbackHandles(
|
||||
_In_ PFN_REQUEST_HANDLER request_callback,
|
||||
_In_ PFN_SHUTDOWN_HANDLER shutdown_callback,
|
||||
_In_ PFN_MANAGED_CONTEXT_HANDLER managed_context_callback,
|
||||
_In_ VOID* pvRequstHandlerContext,
|
||||
_In_ VOID* pvShutdownHandlerContext
|
||||
);
|
||||
|
||||
__override
|
||||
VOID
|
||||
Recycle(
|
||||
VOID
|
||||
);
|
||||
Recycle(
|
||||
VOID
|
||||
);
|
||||
|
||||
// Executes the .NET Core process
|
||||
HRESULT
|
||||
|
|
@ -53,6 +54,11 @@ public:
|
|||
VOID
|
||||
);
|
||||
|
||||
VOID
|
||||
LogErrorsOnMainExit(
|
||||
HRESULT hr
|
||||
);
|
||||
|
||||
REQUEST_NOTIFICATION_STATUS
|
||||
OnAsyncCompletion(
|
||||
DWORD cbCompletion,
|
||||
|
|
@ -67,6 +73,22 @@ public:
|
|||
IN_PROCESS_HANDLER* pInProcessHandler
|
||||
);
|
||||
|
||||
VOID
|
||||
StopCallsIntoManaged(
|
||||
VOID
|
||||
)
|
||||
{
|
||||
m_fBlockCallbacksIntoManaged = TRUE;
|
||||
}
|
||||
|
||||
VOID
|
||||
StopIncomingRequests(
|
||||
VOID
|
||||
)
|
||||
{
|
||||
m_fShutdownCalledFromManaged = TRUE;
|
||||
}
|
||||
|
||||
static
|
||||
IN_PROCESS_APPLICATION*
|
||||
GetInstance(
|
||||
|
|
@ -102,12 +124,13 @@ private:
|
|||
// The exit code of the .NET Core process
|
||||
INT m_ProcessExitCode;
|
||||
|
||||
BOOL m_fManagedAppLoaded;
|
||||
BOOL m_fLoadManagedAppError;
|
||||
BOOL m_fInitialized;
|
||||
BOOL m_fIsWebSocketsConnection;
|
||||
BOOL m_fDoneStdRedirect;
|
||||
BOOL m_fRecycleProcessCalled;
|
||||
volatile BOOL m_fBlockCallbacksIntoManaged;
|
||||
volatile BOOL m_fShutdownCalledFromNative;
|
||||
volatile BOOL m_fShutdownCalledFromManaged;
|
||||
BOOL m_fRecycleCalled;
|
||||
BOOL m_fInitialized;
|
||||
|
||||
FILE* m_pStdFile;
|
||||
STTIMER m_Timer;
|
||||
|
|
@ -138,9 +161,9 @@ private:
|
|||
);
|
||||
|
||||
HRESULT
|
||||
SetEnvironementVariablesOnWorkerProcess(
|
||||
VOID
|
||||
);
|
||||
SetEnvironementVariablesOnWorkerProcess(
|
||||
VOID
|
||||
);
|
||||
|
||||
static
|
||||
INT
|
||||
|
|
|
|||
|
|
@ -423,4 +423,18 @@ http_get_authentication_information(
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
|
||||
VOID
|
||||
http_stop_calls_into_mananged()
|
||||
{
|
||||
IN_PROCESS_APPLICATION::GetInstance()->StopCallsIntoManaged();
|
||||
}
|
||||
|
||||
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
|
||||
VOID
|
||||
http_stop_incoming_requests()
|
||||
{
|
||||
IN_PROCESS_APPLICATION::GetInstance()->StopIncomingRequests();
|
||||
}
|
||||
|
||||
// End of export
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION(
|
|||
{
|
||||
m_status = APPLICATION_STATUS::RUNNING;
|
||||
m_pProcessManager = NULL;
|
||||
//todo
|
||||
InitializeSRWLock(&rwlock);
|
||||
}
|
||||
|
||||
OUT_OF_PROCESS_APPLICATION::~OUT_OF_PROCESS_APPLICATION()
|
||||
|
|
@ -57,10 +57,22 @@ __override
|
|||
VOID
|
||||
OUT_OF_PROCESS_APPLICATION::ShutDown()
|
||||
{
|
||||
if (m_pProcessManager != NULL)
|
||||
AcquireSRWLockExclusive(&rwlock);
|
||||
{
|
||||
m_pProcessManager->ShutdownAllProcesses();
|
||||
m_pProcessManager->DereferenceProcessManager();
|
||||
m_pProcessManager = NULL;
|
||||
if (m_pProcessManager != NULL)
|
||||
{
|
||||
m_pProcessManager->ShutdownAllProcesses();
|
||||
m_pProcessManager->DereferenceProcessManager();
|
||||
m_pProcessManager = NULL;
|
||||
}
|
||||
}
|
||||
ReleaseSRWLockExclusive(&rwlock);
|
||||
}
|
||||
|
||||
__override
|
||||
VOID
|
||||
OUT_OF_PROCESS_APPLICATION::Recycle()
|
||||
{
|
||||
ShutDown();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ public:
|
|||
VOID
|
||||
ShutDown();
|
||||
|
||||
__override
|
||||
VOID
|
||||
Recycle();
|
||||
|
||||
private:
|
||||
PROCESS_MANAGER * m_pProcessManager;
|
||||
SRWLOCK rwlock;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue