// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #include "applicationinfo.h" #include #include "proxymodule.h" #include "hostfxr_utility.h" #include "utility.h" #include "debugutil.h" #include "resources.h" #include "SRWExclusiveLock.h" #include "GlobalVersionUtility.h" #include "exceptions.h" #include "HandleWrapper.h" #include "PollingAppOfflineApplication.h" const PCWSTR APPLICATION_INFO::s_pwzAspnetcoreInProcessRequestHandlerName = L"aspnetcorev2_inprocess.dll"; const PCWSTR APPLICATION_INFO::s_pwzAspnetcoreOutOfProcessRequestHandlerName = L"aspnetcorev2_outofprocess.dll"; APPLICATION_INFO::~APPLICATION_INFO() { if (m_pApplication != NULL) { // shutdown the application m_pApplication->ShutDown(); m_pApplication->DereferenceApplication(); m_pApplication = NULL; } // configuration should be dereferenced after application shutdown // since the former will use it during shutdown if (m_pConfiguration != NULL) { delete m_pConfiguration; m_pConfiguration = NULL; } } HRESULT APPLICATION_INFO::Initialize( _In_ IHttpServer *pServer, _In_ IHttpApplication *pApplication ) { HRESULT hr = S_OK; DBG_ASSERT(pServer); DBG_ASSERT(pApplication); // todo: make sure Initialize should be called only once m_pServer = pServer; FINISHED_IF_NULL_ALLOC(m_pConfiguration = new ASPNETCORE_SHIM_CONFIG()); FINISHED_IF_FAILED(m_pConfiguration->Populate(m_pServer, pApplication)); FINISHED_IF_FAILED(m_struInfoKey.Copy(pApplication->GetApplicationId())); Finished: return hr; } HRESULT APPLICATION_INFO::EnsureApplicationCreated( IHttpContext *pHttpContext ) { HRESULT hr = S_OK; IAPPLICATION *pApplication = NULL; STRU struExeLocation; STRU struHostFxrDllLocation; STACK_STRU(struFileName, 300); // >MAX_PATH if (m_pApplication != nullptr && m_pApplication->QueryStatus() != RECYCLED) { return S_OK; } SRWExclusiveLock lock(m_srwLock); if (m_pApplication != nullptr) { if (m_pApplication->QueryStatus() == RECYCLED) { LOG_INFO("Application went offline"); // Application that went offline // are supposed to recycle themselves m_pApplication->DereferenceApplication(); m_pApplication = nullptr; } else { // another thread created the application FINISHED(S_OK); } } else if (m_fAppCreationAttempted) { // previous CreateApplication failed FINISHED(E_APPLICATION_ACTIVATION_EXEC_FAILURE); } auto& httpApplication = *pHttpContext->GetApplication(); if (PollingAppOfflineApplication::ShouldBeStarted(httpApplication)) { LOG_INFO("Detected app_ofline file, creating polling application"); m_pApplication = new PollingAppOfflineApplication(httpApplication); } else { // Move the request handler check inside of the lock // such that only one request finds and loads it. // FindRequestHandlerAssembly obtains a global lock, but after releasing the lock, // there is a period where we could call m_fAppCreationAttempted = TRUE; FINISHED_IF_FAILED(FindRequestHandlerAssembly(struExeLocation)); if (m_pfnAspNetCoreCreateApplication == NULL) { FINISHED(HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION)); } std::array parameters { {"InProcessExeLocation", struExeLocation.QueryStr()} }; LOG_INFO("Creating handler application"); FINISHED_IF_FAILED(m_pfnAspNetCoreCreateApplication( m_pServer, pHttpContext->GetApplication(), parameters.data(), static_cast(parameters.size()), &pApplication)); m_pApplication = pApplication; } Finished: if (FAILED(hr)) { // Log the failure and update application info to not try again UTILITY::LogEventF(g_hEventLog, EVENTLOG_ERROR_TYPE, ASPNETCORE_EVENT_ADD_APPLICATION_ERROR, ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG, pHttpContext->GetApplication()->GetApplicationId(), hr); } return hr; } HRESULT APPLICATION_INFO::FindRequestHandlerAssembly(STRU& location) { HRESULT hr = S_OK; PCWSTR pstrHandlerDllName; STACK_STRU(struFileName, 256); if (g_fAspnetcoreRHLoadedError) { FINISHED(E_APPLICATION_ACTIVATION_EXEC_FAILURE); } else if (!g_fAspnetcoreRHAssemblyLoaded) { SRWExclusiveLock lock(g_srwLock); if (g_fAspnetcoreRHLoadedError) { FINISHED(E_APPLICATION_ACTIVATION_EXEC_FAILURE); } if (g_fAspnetcoreRHAssemblyLoaded) { FINISHED(S_OK); } if (m_pConfiguration->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) { pstrHandlerDllName = s_pwzAspnetcoreInProcessRequestHandlerName; } else { pstrHandlerDllName = s_pwzAspnetcoreOutOfProcessRequestHandlerName; } // Try to see if RH is already loaded g_hAspnetCoreRH = GetModuleHandle(pstrHandlerDllName); if (g_hAspnetCoreRH == NULL) { if (m_pConfiguration->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) { std::unique_ptr options; FINISHED_IF_FAILED(HOSTFXR_OPTIONS::Create( NULL, m_pConfiguration->QueryProcessPath()->QueryStr(), m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr(), m_pConfiguration->QueryArguments()->QueryStr(), g_hEventLog, options)); FINISHED_IF_FAILED(location.Copy(options->GetExeLocation())); if (FAILED_LOG(hr = FindNativeAssemblyFromHostfxr(options.get(), pstrHandlerDllName, &struFileName))) { UTILITY::LogEventF(g_hEventLog, EVENTLOG_ERROR_TYPE, ASPNETCORE_EVENT_INPROCESS_RH_MISSING, ASPNETCORE_EVENT_INPROCESS_RH_MISSING_MSG, struFileName.IsEmpty() ? s_pwzAspnetcoreInProcessRequestHandlerName : struFileName.QueryStr()); FINISHED(hr); } } else { if (FAILED_LOG(hr = FindNativeAssemblyFromGlobalLocation(pstrHandlerDllName, &struFileName))) { UTILITY::LogEventF(g_hEventLog, EVENTLOG_ERROR_TYPE, ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING, ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG, struFileName.IsEmpty() ? s_pwzAspnetcoreOutOfProcessRequestHandlerName : struFileName.QueryStr()); FINISHED(hr); } } WLOG_INFOF(L"Loading request handler: %s", struFileName.QueryStr()); g_hAspnetCoreRH = LoadLibraryW(struFileName.QueryStr()); if (g_hAspnetCoreRH == NULL) { FINISHED(HRESULT_FROM_WIN32(GetLastError())); } } g_pfnAspNetCoreCreateApplication = (PFN_ASPNETCORE_CREATE_APPLICATION) GetProcAddress(g_hAspnetCoreRH, "CreateApplication"); if (g_pfnAspNetCoreCreateApplication == NULL) { FINISHED(HRESULT_FROM_WIN32(GetLastError())); } g_fAspnetcoreRHAssemblyLoaded = TRUE; } Finished: // // Question: we remember the load failure so that we will not try again. // User needs to check whether the fuction pointer is NULL // m_pfnAspNetCoreCreateApplication = g_pfnAspNetCoreCreateApplication; if (!g_fAspnetcoreRHLoadedError && FAILED(hr)) { g_fAspnetcoreRHLoadedError = TRUE; } return hr; } HRESULT APPLICATION_INFO::FindNativeAssemblyFromGlobalLocation( PCWSTR pstrHandlerDllName, STRU* struFilename ) { HRESULT hr = S_OK; try { std::wstring modulePath = GlobalVersionUtility::GetModuleName(g_hModule); modulePath = GlobalVersionUtility::RemoveFileNameFromFolderPath(modulePath); std::wstring retval = GlobalVersionUtility::GetGlobalRequestHandlerPath(modulePath.c_str(), m_pConfiguration->QueryHandlerVersion()->QueryStr(), pstrHandlerDllName ); RETURN_IF_FAILED(struFilename->Copy(retval.c_str())); } catch (std::exception& e) { STRU struEvent; if (SUCCEEDED(struEvent.Copy(ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG)) && SUCCEEDED(struEvent.AppendA(e.what()))) { UTILITY::LogEvent(g_hEventLog, EVENTLOG_INFORMATION_TYPE, ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING, struEvent.QueryStr()); } hr = E_FAIL; } catch (...) { hr = E_FAIL; } return hr; } // // Tries to find aspnetcorerh.dll from the application // Calls into hostfxr.dll to find it. // Will leave hostfxr.dll loaded as it will be used again to call hostfxr_main. // HRESULT APPLICATION_INFO::FindNativeAssemblyFromHostfxr( HOSTFXR_OPTIONS* hostfxrOptions, PCWSTR libraryName, STRU* struFilename ) { HRESULT hr = S_OK; STRU struApplicationFullPath; STRU struNativeSearchPaths; STRU struNativeDllLocation; HMODULE hmHostFxrDll = NULL; INT intHostFxrExitCode = 0; INT intIndex = -1; INT intPrevIndex = 0; BOOL fFound = FALSE; DWORD dwBufferSize = 1024 * 10; DWORD dwRequiredBufferSize = 0; DBG_ASSERT(struFilename != NULL); FINISHED_LAST_ERROR_IF_NULL(hmHostFxrDll = LoadLibraryW(hostfxrOptions->GetHostFxrLocation())); hostfxr_get_native_search_directories_fn pFnHostFxrSearchDirectories = (hostfxr_get_native_search_directories_fn) GetProcAddress(hmHostFxrDll, "hostfxr_get_native_search_directories"); if (pFnHostFxrSearchDirectories == NULL) { // Host fxr version is incorrect (need a higher version). // TODO log error FINISHED(E_FAIL); } FINISHED_IF_FAILED(hr = struNativeSearchPaths.Resize(dwBufferSize)); while (TRUE) { intHostFxrExitCode = pFnHostFxrSearchDirectories( hostfxrOptions->GetArgc(), hostfxrOptions->GetArgv(), struNativeSearchPaths.QueryStr(), dwBufferSize, &dwRequiredBufferSize ); if (intHostFxrExitCode == 0) { break; } else if (dwRequiredBufferSize > dwBufferSize) { dwBufferSize = dwRequiredBufferSize + 1; // for null terminator FINISHED_IF_FAILED(struNativeSearchPaths.Resize(dwBufferSize)); } else { // Log "Error finding native search directories from aspnetcore application. FINISHED(E_FAIL); } } FINISHED_IF_FAILED(struNativeSearchPaths.SyncWithBuffer()); fFound = FALSE; // The native search directories are semicolon delimited. // Split on semicolons, append aspnetcorerh.dll, and check if the file exists. while ((intIndex = struNativeSearchPaths.IndexOf(L";", intPrevIndex)) != -1) { FINISHED_IF_FAILED(struNativeDllLocation.Copy(&struNativeSearchPaths.QueryStr()[intPrevIndex], intIndex - intPrevIndex)); if (!struNativeDllLocation.EndsWith(L"\\")) { FINISHED_IF_FAILED(struNativeDllLocation.Append(L"\\")); } FINISHED_IF_FAILED(struNativeDllLocation.Append(libraryName)); if (UTILITY::CheckIfFileExists(struNativeDllLocation.QueryStr())) { FINISHED_IF_FAILED(struFilename->Copy(struNativeDllLocation)); fFound = TRUE; break; } intPrevIndex = intIndex + 1; } if (!fFound) { FINISHED(E_FAIL); } Finished: if (FAILED(hr) && hmHostFxrDll != NULL) { FreeLibrary(hmHostFxrDll); } return hr; } VOID APPLICATION_INFO::RecycleApplication() { IAPPLICATION* pApplication; HANDLE hThread = INVALID_HANDLE_VALUE; if (m_pApplication != NULL) { SRWExclusiveLock lock(m_srwLock); if (m_pApplication != NULL) { pApplication = m_pApplication; if (m_pConfiguration->QueryHostingModel() == HOSTING_OUT_PROCESS) { // // For inprocess, need to set m_pApplication to NULL first to // avoid mapping new request to the recycled application. // Outofprocess application instance will be created for new request // For inprocess, as recycle will lead to shutdown later, leave m_pApplication // to not block incoming requests till worker process shutdown // m_pApplication = NULL; } else { // // For inprocess, need hold the application till shutdown is called // Bump the reference counter as DoRecycleApplication will do dereference // pApplication->ReferenceApplication(); } 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 } else { if (m_pConfiguration->QueryHostingModel() == HOSTING_IN_PROCESS) { // In process application failed to start for whatever reason, need to recycle the work process m_pServer->RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); } } if (hThread == NULL) { if (!g_fRecycleProcessCalled) { g_fRecycleProcessCalled = TRUE; g_pHttpServer->RecycleProcess(L"On Demand by AspNetCore Module for recycle application failure"); } } else { // Closing a thread handle does not terminate the associated thread or remove the thread object. CloseHandle(hThread); } } } VOID APPLICATION_INFO::DoRecycleApplication( LPVOID lpParam) { IAPPLICATION* pApplication = static_cast(lpParam); // No lock required if (pApplication != NULL) { // Recycle will call shutdown for out of process pApplication->Recycle(); // Decrement the ref count as we reference it in RecycleApplication. pApplication->DereferenceApplication(); } } VOID APPLICATION_INFO::ShutDownApplication() { IAPPLICATION* pApplication = NULL; // pApplication can be NULL due to app_offline if (m_pApplication != NULL) { SRWExclusiveLock lock(m_srwLock); if (m_pApplication != NULL) { pApplication = m_pApplication; // Set m_pApplication to NULL first to prevent anyone from using it m_pApplication = NULL; pApplication->ShutDown(); pApplication->DereferenceApplication(); } } }