// 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 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(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 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 hostFxrOptions; auto context = std::make_shared(); 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(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 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& application) { try { std::unique_ptr options; THROW_IF_FAILED(InProcessOptions::Create(pServer, pHttpApplication, options)); application = std::unique_ptr( 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& 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 &context) { // Keep aspnetcorev2_inprocess.dll loaded while this thread is running // this is required because thread might be abandoned HandleWrapper 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(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(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 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; }