text: // 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 "HostFxrResolutionResult.h"
  #include "requesthandler_config.h"
  #include "environmentvariablehelpers.h"
  #include "exceptions.h"
  #include "LoggingHelpers.h"
  #include "resources.h"
  #include "EventLog.h"
  #include "ModuleHelpers.h"
  #include "Environment.h"
  #include "HostFxr.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)),
      m_requestCount(0)
  {
      DBG_ASSERT(m_pConfig);
  
      const auto knownLocation = FindParameter<PCWSTR>(s_exeLocationParameterName, pParameters, nParameters);
      if (knownLocation != nullptr)
      {
          m_dotnetExeKnownLocation = knownLocation;
      }
  
      m_stringRedirectionOutput = std::make_shared<StringStreamRedirectionOutput>();
  }
  
  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()
  {
      // This has the state lock around it.
      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);
          }
  
          SRWSharedLock dataLock(m_dataLock);
  
          auto requestCount = m_requestCount.load();
  
          if (requestCount == 0)
          {
              CallRequestsDrained();
          }
      }
  
      // 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_ PFN_REQUESTS_DRAINED_HANDLER requestsDrainedHandler,
      _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_RequestsDrainedHandler = requestsDrainedHandler;
  
      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(ErrorContext& errorContext)
  {
      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
  
      LOG_INFO(L"Waiting for initialization");
  
      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));
  
      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
          errorContext.statusCode = 500;
          errorContext.subStatusCode = 37;
          errorContext.generalErrorType = "ANCM Failed to Start Within Startup Time Limit";
          errorContext.errorReason = format("ANCM failed to start after %d milliseconds", m_pConfig->QueryStartupTimeLimitInMS());
  
          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<HostFxrResolutionResult> hostFxrResolutionResult;
  
          auto context = std::make_shared<ExecuteClrContext>();
  
          ErrorContext errorContext; // unused 
  
          if (s_fMainCallback == nullptr)
          {
              THROW_IF_FAILED(HostFxrResolutionResult::Create(
                  m_dotnetExeKnownLocation,
                  m_pConfig->QueryProcessPath(),
                  QueryApplicationPhysicalPath(),
                  m_pConfig->QueryArguments(),
                  errorContext,
                  hostFxrResolutionResult
                  ));
  
              hostFxrResolutionResult->GetArguments(context->m_argc, context->m_argv);
              THROW_IF_FAILED(SetEnvironmentVariablesOnWorkerProcess());
              context->m_hostFxr.Load(hostFxrResolutionResult->GetHostFxrLocation());
          }
          else
          {
              context->m_hostFxr.SetMain(s_fMainCallback);
          }
  
          // 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;
  
          if (m_pConfig->QuerySetCurrentDirectory())
          {
              auto dllDirectory = Environment::GetDllDirectoryValue();
              auto currentDirectory = Environment::GetCurrentDirectoryValue();
  
              LOG_INFOF(L"Initial Dll directory: '%s', current directory: '%s'", dllDirectory.c_str(), currentDirectory.c_str());
  
              // If DllDirectory wasn't set change it to previous current directory value
              if (dllDirectory.empty())
              {
                  LOG_LAST_ERROR_IF(!SetDllDirectory(currentDirectory.c_str()));
                  LOG_INFOF(L"Setting dll directory to %s", currentDirectory.c_str());
              }
  
              LOG_LAST_ERROR_IF(!SetCurrentDirectory(this->QueryApplicationPhysicalPath().c_str()));
  
              LOG_INFOF(L"Setting current directory to %s", this->QueryApplicationPhysicalPath().c_str());
          }
  
          auto startupReturnCode = context->m_hostFxr.InitializeForApp(context->m_argc, context->m_argv.get(), m_dotnetExeKnownLocation);
          if (startupReturnCode != 0)
          {
              throw InvalidOperationException(format(L"Error occured when initializing inprocess application, Return code: 0x%x", startupReturnCode));
          }
  
          if (m_pConfig->QueryCallStartupHook())
          {
              PWSTR startupHookValue = NULL;
              // Will get property not found if the enviroment variable isn't set.
              context->m_hostFxr.GetRuntimePropertyValue(DOTNETCORE_STARTUP_HOOK, &startupHookValue);
              
              if (startupHookValue == NULL)
              {
                  RETURN_IF_NOT_ZERO(context->m_hostFxr.SetRuntimePropertyValue(DOTNETCORE_STARTUP_HOOK, ASPNETCORE_STARTUP_ASSEMBLY));
              }
              else
              {
                  std::wstring startupHook(startupHookValue);
                  startupHook.append(L";").append(ASPNETCORE_STARTUP_ASSEMBLY);
                  RETURN_IF_NOT_ZERO(context->m_hostFxr.SetRuntimePropertyValue(DOTNETCORE_STARTUP_HOOK, startupHook.c_str()));
              }
          }
  
          RETURN_IF_NOT_ZERO(context->m_hostFxr.SetRuntimePropertyValue(DOTNETCORE_USE_ENTRYPOINT_FILTER, L"1"));
          RETURN_IF_NOT_ZERO(context->m_hostFxr.SetRuntimePropertyValue(DOTNETCORE_STACK_SIZE, m_pConfig->QueryStackSize().c_str()));
  
          bool clrThreadExited;
          {
              auto redirectionOutput = LoggingHelpers::CreateOutputs(
                      m_pConfig->QueryStdoutLogEnabled(),
                      m_pConfig->QueryStdoutLogFile(),
                      QueryApplicationPhysicalPath(),
                      m_stringRedirectionOutput
                  );
  
              StandardStreamRedirection redirection(*redirectionOutput.get(), m_pHttpServer.IsCommandLineLaunch());
  
              context->m_redirectionOutput = redirectionOutput.get();
  
              //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);
  
              // Disconnect output
              context->m_redirectionOutput = nullptr;
  
              THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
  
              LOG_INFOF(L"Starting shutdown sequence %d", waitResult);
  
              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(clrWaitResult == 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();
  
          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_FAILURE,
              ASPNETCORE_EVENT_LOAD_CLR_FAILURE_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_FAILURE,
              ASPNETCORE_EVENT_LOAD_CLR_FAILURE_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,
      IHttpSite* pSite,
      IHttpApplication& pHttpApplication,
      APPLICATION_PARAMETER* pParameters,
      DWORD nParameters,
      std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER>& application,
      ErrorContext& errorContext)
  {
      try
      {
          std::unique_ptr<InProcessOptions> options;
          THROW_IF_FAILED(InProcessOptions::Create(pServer, pSite, 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(errorContext));
          return S_OK;
      }
      catch (InvalidOperationException& ex)
      {
          EventLog::Error(
              ASPNETCORE_EVENT_LOAD_CLR_FAILURE,
              ASPNETCORE_EVENT_LOAD_CLR_FAILURE_MSG,
              pHttpApplication.GetApplicationId(),
              pHttpApplication.GetApplicationPhysicalPath(),
              ex.as_wstring().c_str());
  
          RETURN_CAUGHT_EXCEPTION();
      }
      catch (std::runtime_error& ex)
      {
          EventLog::Error(
              ASPNETCORE_EVENT_LOAD_CLR_FAILURE,
              ASPNETCORE_EVENT_LOAD_CLR_FAILURE_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_hostFxr.Main(context->m_argc, context->m_argv.get());
  
          LOG_INFOF(L"Managed application exited with code %d", exitCode);
  
          context->m_exitCode = exitCode;
          context->m_hostFxr.Close();
      }
      __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)
  {
      HandleWrapper<ModuleHandleTraits> moduleHandle;
  
      // Keep aspnetcorev2_inprocess.dll loaded while this thread is running
      // this is required because thread might be abandoned
      ModuleHelpers::IncrementCurrentModuleRefCount(moduleHandle);
  
      // Nested block is required here because FreeLibraryAndExitThread would prevent destructors from running
      // so we need to do in in a nested scope
      {
          // We use forwarder here instead of context->m_errorWriter itself to be able to
          // disconnect listener before CLR exits
          ForwardingRedirectionOutput redirectionForwarder(&context->m_redirectionOutput);
          const auto redirect = context->m_hostFxr.RedirectOutput(&redirectionForwarder);
  
          ExecuteClr(context);
      }
      FreeLibraryAndExitThread(moduleHandle.release(), 0);
  }
  
  HRESULT
  IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess()
  {
      auto variables = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable(
          m_pConfig->QueryEnvironmentVariables(),
          m_pConfig->QueryWindowsAuthEnabled(),
          m_pConfig->QueryBasicAuthEnabled(),
          m_pConfig->QueryAnonymousAuthEnabled(),
          false, // fAddHostingStartup
          QueryApplicationPhysicalPath().c_str(),
          nullptr);
  
      for (const auto & variable : variables)
      {
          LOG_INFOF(L"Setting environment variable %ls=%ls", variable.first.c_str(), variable.second.c_str());
          SetEnvironmentVariable(variable.first.c_str(), variable.second.c_str());
      }
  
      return S_OK;
  }
  
  VOID
  IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) const
  {
      auto content = m_stringRedirectionOutput->GetOutput();
  
      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
      {
          SRWSharedLock dataLock(m_dataLock);
  
          DBG_ASSERT(!m_fStopCalled);
          m_requestCount++;
  
          LOG_TRACEF(L"Adding request. Total Request Count %d", m_requestCount.load());
  
          *pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_DisconnectHandler, m_AsyncCompletionHandler);
      }
      CATCH_RETURN();
  
      return S_OK;
  }
  
  void
  IN_PROCESS_APPLICATION::HandleRequestCompletion()
  {
      SRWSharedLock dataLock(m_dataLock);
  
      auto requestCount = --m_requestCount;
  
      LOG_TRACEF(L"Removing Request %d", requestCount);
  
      if (m_fStopCalled && requestCount == 0 && !m_blockManagedCallbacks)
      {
          CallRequestsDrained();
      }
  }
  
  void IN_PROCESS_APPLICATION::CallRequestsDrained()
  {
      // Atomic swap these.
      auto handler = m_RequestsDrainedHandler.exchange(nullptr);
      if (handler != nullptr)
      {
          LOG_INFO(L"Drained all requests, notifying managed.");
          handler(m_ShutdownHandlerContext);
      }
  }
  