511 lines
16 KiB
C++
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;
|
|
}
|