aspnetcore/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp

511 lines
16 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 "inprocessapplication.h"
#include "inprocesshandler.h"
#include "hostfxroptions.h"
#include "requesthandler_config.h"
#include "environmentvariablehelpers.h"
#include "exceptions.h"
#include "LoggingHelpers.h"
#include "resources.h"
#include "EventLog.h"
#include "ModuleHelpers.h"
IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL;
IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
IHttpServer& pHttpServer,
IHttpApplication& pApplication,
std::unique_ptr<InProcessOptions> pConfig,
APPLICATION_PARAMETER *pParameters,
DWORD nParameters) :
InProcessApplicationBase(pHttpServer, pApplication),
m_Initialized(false),
m_blockManagedCallbacks(true),
m_waitForShutdown(true),
m_pConfig(std::move(pConfig))
{
DBG_ASSERT(m_pConfig);
for (DWORD i = 0; i < nParameters; i++)
{
if (_stricmp(pParameters[i].pzName, s_exeLocationParameterName) == 0)
{
m_dotnetExeKnownLocation = reinterpret_cast<PCWSTR>(pParameters[i].pValue);
}
}
}
IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
{
s_Application = nullptr;
}
VOID
IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated)
{
StopClr();
InProcessApplicationBase::StopInternal(fServerInitiated);
}
VOID
IN_PROCESS_APPLICATION::StopClr()
{
LOG_INFO(L"Stopping CLR");
if (!m_blockManagedCallbacks)
{
// We cannot call into managed if the dll is detaching from the process.
// Calling into managed code when the dll is detaching is strictly a bad idea,
// and usually results in an AV saying "The string binding is invalid"
const auto shutdownHandler = m_ShutdownHandler;
if (!g_fProcessDetach && shutdownHandler != nullptr)
{
shutdownHandler(m_ShutdownHandlerContext);
}
}
// Signal shutdown
if (m_pShutdownEvent != nullptr)
{
LOG_IF_FAILED(SetEvent(m_pShutdownEvent));
}
if (m_workerThread.joinable())
{
// Worker thread would wait for clr to finish and log error if required
m_workerThread.join();
}
s_Application = nullptr;
}
VOID
IN_PROCESS_APPLICATION::SetCallbackHandles(
_In_ PFN_REQUEST_HANDLER request_handler,
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
_In_ PFN_DISCONNECT_HANDLER disconnect_callback,
_In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler,
_In_ VOID* pvRequstHandlerContext,
_In_ VOID* pvShutdownHandlerContext
)
{
LOG_INFO(L"In-process callbacks set");
m_RequestHandler = request_handler;
m_RequestHandlerContext = pvRequstHandlerContext;
m_DisconnectHandler = disconnect_callback;
m_ShutdownHandler = shutdown_handler;
m_ShutdownHandlerContext = pvShutdownHandlerContext;
m_AsyncCompletionHandler = async_completion_handler;
m_blockManagedCallbacks = false;
m_Initialized = true;
// Can't check the std err handle as it isn't a critical error
// Initialization complete
EventLog::Info(
ASPNETCORE_EVENT_INPROCESS_START_SUCCESS,
ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG,
QueryApplicationPhysicalPath().c_str());
SetEvent(m_pInitializeEvent);
}
HRESULT
IN_PROCESS_APPLICATION::LoadManagedApplication()
{
THROW_LAST_ERROR_IF_NULL(m_pInitializeEvent = CreateEvent(
nullptr, // default security attributes
TRUE, // manual reset event
FALSE, // not set
nullptr)); // name
THROW_LAST_ERROR_IF_NULL(m_pShutdownEvent = CreateEvent(
nullptr, // default security attributes
TRUE, // manual reset event
FALSE, // not set
nullptr)); // name
m_workerThread = std::thread([](std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER> application)
{
LOG_INFO(L"Starting in-process worker thread");
application->ExecuteApplication();
LOG_INFO(L"Stopping in-process worker thread");
}, ::ReferenceApplication(this));
LOG_INFO(L"Waiting for initialization");
const HANDLE waitHandles[2] = { m_pInitializeEvent, m_workerThread.native_handle() };
// Wait for shutdown request
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, m_pConfig->QueryStartupTimeLimitInMS());
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
if (waitResult == WAIT_TIMEOUT)
{
// If server wasn't initialized in time shut application down without waiting for CLR thread to exit
m_waitForShutdown = false;
StopClr();
throw InvalidOperationException(format(L"Managed server didn't initialize after %u ms.", m_pConfig->QueryStartupTimeLimitInMS()));
}
// WAIT_OBJECT_0 + 1 is the worker thead handle
if (waitResult == WAIT_OBJECT_0 + 1)
{
// Worker thread exited stop
StopClr();
throw InvalidOperationException(format(L"CLR worker thread exited prematurely"));
}
THROW_IF_FAILED(StartMonitoringAppOffline());
return S_OK;
}
void
IN_PROCESS_APPLICATION::ExecuteApplication()
{
try
{
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions;
auto context = std::make_shared<ExecuteClrContext>();
auto pProc = s_fMainCallback;
if (pProc == nullptr)
{
HMODULE hModule;
// hostfxr should already be loaded by the shim. If not, then we will need
// to load it ourselves by finding hostfxr again.
THROW_LAST_ERROR_IF_NULL(hModule = GetModuleHandle(L"hostfxr.dll"));
// Get the entry point for main
pProc = reinterpret_cast<hostfxr_main_fn>(GetProcAddress(hModule, "hostfxr_main"));
THROW_LAST_ERROR_IF_NULL(pProc);
THROW_IF_FAILED(HOSTFXR_OPTIONS::Create(
m_dotnetExeKnownLocation,
m_pConfig->QueryProcessPath(),
QueryApplicationPhysicalPath(),
m_pConfig->QueryArguments(),
hostFxrOptions
));
hostFxrOptions->GetArguments(context->m_argc, context->m_argv);
THROW_IF_FAILED(SetEnvironmentVariablesOnWorkerProcess());
}
context->m_pProc = pProc;
if (m_pLoggerProvider == nullptr)
{
THROW_IF_FAILED(LoggingHelpers::CreateLoggingProvider(
m_pConfig->QueryStdoutLogEnabled(),
!m_pHttpServer.IsCommandLineLaunch(),
m_pConfig->QueryStdoutLogFile().c_str(),
QueryApplicationPhysicalPath().c_str(),
m_pLoggerProvider));
m_pLoggerProvider->TryStartRedirection();
}
// There can only ever be a single instance of .NET Core
// loaded in the process but we need to get config information to boot it up in the
// first place. This is happening in an execute request handler and everyone waits
// until this initialization is done.
// We set a static so that managed code can call back into this instance and
// set the callbacks
s_Application = this;
//Start CLR thread
m_clrThread = std::thread(ClrThreadEntryPoint, context);
// Wait for thread exit or shutdown event
const HANDLE waitHandles[2] = { m_pShutdownEvent, m_clrThread.native_handle() };
// Wait for shutdown request
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
LOG_INFOF(L"Starting shutdown sequence %d", waitResult);
bool clrThreadExited = waitResult == (WAIT_OBJECT_0 + 1);
// shutdown was signaled
// only wait for shutdown in case of successful startup
if (m_waitForShutdown)
{
const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS());
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
}
LOG_INFOF(L"Clr thread wait ended: clrThreadExited: %d", clrThreadExited);
// At this point CLR thread either finished or timed out, abandon it.
m_clrThread.detach();
m_pLoggerProvider->TryStopRedirection();
if (m_fStopCalled)
{
if (clrThreadExited)
{
EventLog::Info(
ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL,
ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG,
QueryConfigPath().c_str());
}
else
{
EventLog::Warn(
ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE,
ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG,
QueryConfigPath().c_str());
}
}
else
{
if (clrThreadExited)
{
UnexpectedThreadExit(*context);
// If the inprocess server was initialized, we need to cause recycle to be called on the worker process.
// in case when it was not initialized we need to keep server running to serve 502 page
if (m_Initialized)
{
QueueStop();
}
}
}
}
catch (InvalidOperationException& ex)
{
EventLog::Error(
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
ex.as_wstring().c_str());
OBSERVE_CAUGHT_EXCEPTION();
}
catch (std::runtime_error& ex)
{
EventLog::Error(
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
GetUnexpectedExceptionMessage(ex).c_str());
OBSERVE_CAUGHT_EXCEPTION();
}
}
void IN_PROCESS_APPLICATION::QueueStop()
{
if (m_fStopCalled)
{
return;
}
LOG_INFO(L"Queueing in-process stop thread");
std::thread stoppingThread([](std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER> application)
{
LOG_INFO(L"Starting in-process stop thread");
application->Stop(false);
LOG_INFO(L"Stopping in-process stop thread");
}, ::ReferenceApplication(this));
stoppingThread.detach();
}
HRESULT IN_PROCESS_APPLICATION::Start(
IHttpServer& pServer,
IHttpApplication& pHttpApplication,
APPLICATION_PARAMETER* pParameters,
DWORD nParameters,
std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER>& application)
{
try
{
std::unique_ptr<InProcessOptions> options;
THROW_IF_FAILED(InProcessOptions::Create(pServer, pHttpApplication, options));
application = std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER>(
new IN_PROCESS_APPLICATION(pServer, pHttpApplication, std::move(options), pParameters, nParameters));
THROW_IF_FAILED(application->LoadManagedApplication());
return S_OK;
}
catch (InvalidOperationException& ex)
{
EventLog::Error(
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
pHttpApplication.GetApplicationId(),
pHttpApplication.GetApplicationPhysicalPath(),
ex.as_wstring().c_str());
RETURN_CAUGHT_EXCEPTION();
}
catch (std::runtime_error& ex)
{
EventLog::Error(
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
pHttpApplication.GetApplicationId(),
pHttpApplication.GetApplicationPhysicalPath(),
GetUnexpectedExceptionMessage(ex).c_str());
RETURN_CAUGHT_EXCEPTION();
}
CATCH_RETURN();
}
// Required because __try and objects with destructors can not be mixed
void
IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr<ExecuteClrContext>& context)
{
__try
{
auto const exitCode = context->m_pProc(context->m_argc, context->m_argv.get());
LOG_INFOF(L"Managed application exited with code %d", exitCode);
context->m_exitCode = exitCode;
}
__except(GetExceptionCode() != 0)
{
LOG_INFOF(L"Managed threw an exception %d", GetExceptionCode());
context->m_exceptionCode = GetExceptionCode();
}
}
//
// Calls hostfxr_main with the hostfxr and application as arguments.
// This method should not access IN_PROCESS_APPLICATION instance as it may be already freed
// in case of startup timeout
//
VOID
IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrContext> &context)
{
// Keep aspnetcorev2_inprocess.dll loaded while this thread is running
// this is required because thread might be abandoned
HandleWrapper<ModuleHandleTraits> moduleHandle;
ModuleHelpers::IncrementCurrentModuleRefCount(moduleHandle);
ExecuteClr(context);
FreeLibraryAndExitThread(moduleHandle.release(), 0);
}
HRESULT
IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess()
{
auto variables = m_pConfig->QueryEnvironmentVariables();
auto inputTable = std::unique_ptr<ENVIRONMENT_VAR_HASH, ENVIRONMENT_VAR_HASH_DELETER>(new ENVIRONMENT_VAR_HASH());
RETURN_IF_FAILED(inputTable->Initialize(37 /*prime*/));
// Copy environment variables to old style hash table
for (auto & variable : variables)
{
auto pNewEntry = std::unique_ptr<ENVIRONMENT_VAR_ENTRY, ENVIRONMENT_VAR_ENTRY_DELETER>(new ENVIRONMENT_VAR_ENTRY());
RETURN_IF_FAILED(pNewEntry->Initialize((variable.first + L"=").c_str(), variable.second.c_str()));
RETURN_IF_FAILED(inputTable->InsertRecord(pNewEntry.get()));
}
ENVIRONMENT_VAR_HASH* pHashTable = NULL;
std::unique_ptr<ENVIRONMENT_VAR_HASH, ENVIRONMENT_VAR_HASH_DELETER> table;
RETURN_IF_FAILED(ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable(
inputTable.get(),
m_pConfig->QueryWindowsAuthEnabled(),
m_pConfig->QueryBasicAuthEnabled(),
m_pConfig->QueryAnonymousAuthEnabled(),
&pHashTable));
table.reset(pHashTable);
HRESULT hr = S_OK;
table->Apply(ENVIRONMENT_VAR_HELPERS::AppendEnvironmentVariables, &hr);
RETURN_IF_FAILED(hr);
table->Apply(ENVIRONMENT_VAR_HELPERS::SetEnvironmentVariables, &hr);
RETURN_IF_FAILED(hr);
return S_OK;
}
VOID
IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) const
{
auto content = m_pLoggerProvider->GetStdOutContent();
if (context.m_exceptionCode != 0)
{
if (!content.empty())
{
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_STDOUT_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
context.m_exceptionCode,
content.c_str());
}
else
{
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
context.m_exceptionCode
);
}
return;
}
//
// 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.
//
if (!content.empty())
{
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
context.m_exitCode,
content.c_str());
}
else
{
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
context.m_exitCode);
}
}
HRESULT
IN_PROCESS_APPLICATION::CreateHandler(
_In_ IHttpContext *pHttpContext,
_Out_ IREQUEST_HANDLER **pRequestHandler)
{
try
{
*pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_DisconnectHandler, m_AsyncCompletionHandler);
}
CATCH_RETURN();
return S_OK;
}