From 45ca6a0bcdcad75a17ff404734ec6017484355ae Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 31 May 2019 10:55:58 -0700 Subject: [PATCH 01/15] work --- .../AspNetCore/AppOfflineHandler.h | 4 +- .../AspNetCore/ApplicationFactory.h | 6 +- .../AspNetCore/AspNetCore.vcxproj | 10 +- .../AspNetCore/HandlerResolver.cpp | 14 +- .../AspNetCore/HandlerResolver.h | 5 +- .../AspNetCore/ShimOptions.cpp | 14 +- .../AspNetCore/ShimOptions.h | 21 ++ .../AspNetCore/applicationinfo.cpp | 107 ++++++- .../AspNetCore/applicationinfo.h | 4 + .../AspNetCore/applicationmanager.cpp | 43 ++- .../AspNetCoreModuleV2/AspNetCore/dllmain.cpp | 1 + .../AspNetCore/proxymodule.cpp | 7 +- .../CommonLib/Environment.cpp | 84 ++++++ .../CommonLib/Environment.h | 7 + .../PollingAppOfflineApplication.cpp | 1 + .../CommonLib/application.h | 2 +- .../CommonLib/aspnetcore_msg.mc | 6 + .../AspNetCoreModuleV2/CommonLib/resources.h | 1 + .../InProcessApplicationBase.cpp | 6 +- .../InProcessApplicationBase.h | 3 +- .../ShuttingDownApplication.h | 2 +- .../StartupExceptionApplication.h | 2 +- .../inprocessapplication.cpp | 41 ++- .../inprocessapplication.h | 2 + .../outprocessapplication.cpp | 3 +- .../AppOfflineTrackingApplication.cpp | 26 +- .../AppOfflineTrackingApplication.h | 6 +- .../RequestHandlerLib/filewatcher.cpp | 153 +++++++++- .../RequestHandlerLib/filewatcher.h | 22 ++ .../Infrastructure/EventLogHelpers.cs | 5 + .../Common.FunctionalTests/ShadowCopyTests.cs | 261 ++++++++++++++++++ src/Servers/IIS/IISIntegration.slnf | 1 + src/Servers/IIS/copyToIIS.ps1 | 4 + 33 files changed, 818 insertions(+), 56 deletions(-) create mode 100644 src/Servers/IIS/IIS/test/Common.FunctionalTests/ShadowCopyTests.cs create mode 100644 src/Servers/IIS/copyToIIS.ps1 diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AppOfflineHandler.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AppOfflineHandler.h index 33afc803bc87..e7da69eff2f6 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AppOfflineHandler.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AppOfflineHandler.h @@ -11,8 +11,8 @@ class AppOfflineHandler: public REQUEST_HANDLER public: AppOfflineHandler(IHttpContext& pContext, const std::string& appOfflineContent) : REQUEST_HANDLER(pContext), - m_pContext(pContext), - m_strAppOfflineContent(appOfflineContent) + m_pContext(pContext), + m_strAppOfflineContent(appOfflineContent) { } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h index 23c5a67d409f..6e7caa7912cb 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h @@ -32,15 +32,17 @@ class ApplicationFactory HRESULT Execute( _In_ IHttpServer *pServer, _In_ IHttpContext *pHttpContext, + _In_ std::wstring shadowCopyDirectory, _Outptr_ IAPPLICATION **pApplication) const { // m_location.data() is const ptr copy to local to get mutable pointer auto location = m_location; - std::array parameters { + std::array parameters { { {"InProcessExeLocation", location.data()}, {"TraceContext", pHttpContext->GetTraceContext()}, - {"Site", pHttpContext->GetSite()} + {"Site", pHttpContext->GetSite()}, + {"ShadowCopyDirectory", shadowCopyDirectory.data()} } }; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj index da88b26f4c47..187df260c1f8 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj @@ -111,7 +111,7 @@ Windows true - kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib Source.def UseLinkTimeCodeGeneration @@ -147,7 +147,7 @@ Windows true - kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib Source.def UseLinkTimeCodeGeneration @@ -188,7 +188,7 @@ /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true Source.def - kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib UseLinkTimeCodeGeneration @@ -228,7 +228,7 @@ /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true Source.def - kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib UseLinkTimeCodeGeneration @@ -317,4 +317,4 @@ - + \ No newline at end of file diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp index 2a1303bcdf2f..04985267d17f 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp @@ -29,6 +29,7 @@ HandlerResolver::HandlerResolver(HMODULE hModule, const IHttpServer &pServer) HRESULT HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication, + std::filesystem::path shadowCopyPath, const ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory, ErrorContext& errorContext) @@ -62,7 +63,7 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication RETURN_IF_FAILED(HostFxrResolutionResult::Create( L"", pConfiguration.QueryProcessPath(), - pApplication.GetApplicationPhysicalPath(), + shadowCopyPath.empty() ? pApplication.GetApplicationPhysicalPath() : shadowCopyPath, pConfiguration.QueryArguments(), errorContext, options)); @@ -125,7 +126,7 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication } HRESULT -HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, std::unique_ptr& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext) +HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, std::filesystem::path shadowCopyPath, std::unique_ptr& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext) { SRWExclusiveLock lock(m_requestHandlerLoadLock); if (m_loadedApplicationHostingModel != HOSTING_UNKNOWN) @@ -168,7 +169,7 @@ HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, std m_loadedApplicationHostingModel = options.QueryHostingModel(); m_loadedApplicationId = pApplication.GetApplicationId(); - RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, options, pApplicationFactory, errorContext)); + RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, shadowCopyPath, options, pApplicationFactory, errorContext)); return S_OK; } @@ -181,6 +182,13 @@ void HandlerResolver::ResetHostingModel() m_loadedApplicationId.resize(0); } +APP_HOSTING_MODEL HandlerResolver::GetHostingModel() +{ + SRWExclusiveLock lock(m_requestHandlerLoadLock); + + return m_loadedApplicationHostingModel; +} + HRESULT HandlerResolver::FindNativeAssemblyFromGlobalLocation( const ShimOptions& pConfiguration, diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h index 59e1b01ac63b..ed36df3c4819 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h @@ -15,11 +15,12 @@ class HandlerResolver { public: HandlerResolver(HMODULE hModule, const IHttpServer &pServer); - HRESULT GetApplicationFactory(const IHttpApplication &pApplication, std::unique_ptr& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext); + HRESULT GetApplicationFactory(const IHttpApplication &pApplication, std::filesystem::path shadowCopyPath, std::unique_ptr& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext); void ResetHostingModel(); + APP_HOSTING_MODEL GetHostingModel(); private: - HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory, ErrorContext& errorContext); + HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, std::filesystem::path shadowCopyPath, const ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory, ErrorContext& errorContext); HRESULT FindNativeAssemblyFromGlobalLocation(const ShimOptions& pConfiguration, PCWSTR libraryName, std::wstring& handlerDllPath); HRESULT FindNativeAssemblyFromHostfxr( const HostFxrResolutionResult& hostfxrOptions, diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp index 353b0be78866..efa16875373e 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp @@ -8,6 +8,9 @@ #include "Environment.h" #define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion" +#define CS_ASPNETCORE_SHADOW_COPY L"enableShadowCopy" +#define CS_ASPNETCORE_SHADOW_COPY_DIRECTORY L"shadowCopyDirectory" +#define CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT L"cleanShadowCopyDirectory" ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : m_hostingModel(HOSTING_UNKNOWN), @@ -31,12 +34,21 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : "or hostingModel=\"outofprocess\" in the web.config file.", hostingModel.c_str())); } + const auto handlerSettings = section->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS); + if (m_hostingModel == HOSTING_OUT_PROCESS) { - const auto handlerSettings = section->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS); m_strHandlerVersion = find_element(handlerSettings, CS_ASPNETCORE_HANDLER_VERSION).value_or(std::wstring()); } + auto enableShadowCopyElement = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY).value_or(std::wstring()); + m_fEnableShadowCopying = equals_ignore_case(L"true", enableShadowCopyElement); + + auto cleanShadowCopyDirectory = find_element(handlerSettings, CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT).value_or(std::wstring()); + m_fCleanShadowCopyDirectory = equals_ignore_case(L"true", cleanShadowCopyDirectory); + + m_strShadowCopyingDirectory = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY_DIRECTORY).value_or(std::wstring()); + m_strProcessPath = section->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH); m_strArguments = section->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT); m_fStdoutLogEnabled = section->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h index 5d1c04f8ab07..76a7113e5e91 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h @@ -65,6 +65,24 @@ class ShimOptions: NonCopyable return m_fShowDetailedErrors; } + bool + QueryShadowCopyEnabled() const noexcept + { + return m_fEnableShadowCopying; + } + + bool + QueryCleanShadowCopyDirectory() const noexcept + { + return m_fCleanShadowCopyDirectory; + } + + const std::wstring& + QueryShadowCopyDirectory() const noexcept + { + return m_strShadowCopyingDirectory; + } + ShimOptions(const ConfigurationSource &configurationSource); private: @@ -76,4 +94,7 @@ class ShimOptions: NonCopyable bool m_fStdoutLogEnabled; bool m_fDisableStartupPage; bool m_fShowDetailedErrors; + bool m_fEnableShadowCopying; + bool m_fCleanShadowCopyDirectory; + std::wstring m_strShadowCopyingDirectory; }; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index 994061cadcfb..e944793da315 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -3,6 +3,8 @@ #include "applicationinfo.h" +#include "rpcdce.h" +#include "Rpc.h" #include "proxymodule.h" #include "HostFxrResolver.h" #include "debugutil.h" @@ -18,6 +20,7 @@ #include "file_utility.h" extern HINSTANCE g_hServerModule; +extern BOOL g_fInAppOfflineShutdown; HRESULT APPLICATION_INFO::CreateHandler( @@ -49,7 +52,6 @@ APPLICATION_INFO::CreateHandler( while (hr != S_OK) { // At this point application is either null or shutdown and is returning S_FALSE - if (m_pApplication != nullptr) { LOG_INFO(L"Application went offline"); @@ -80,6 +82,20 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext) return S_OK; } + + if (g_fInAppOfflineShutdown) + { + m_pApplication = make_application( + pHttpApplication, + E_FAIL, + false /* disableStartupPage */, + "" /* responseContent */, + 503i16 /* statusCode */, + 0i16 /* subStatusCode */, + "Application Shutting Down"); + return S_OK; + } + try { const WebConfigConfigurationSource configurationSource(m_pServer.GetAdminManager(), pHttpApplication); @@ -93,6 +109,7 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext) if (FAILED_LOG(hr)) { + OBSERVE_CAUGHT_EXCEPTION(); EventLog::Error( ASPNETCORE_EVENT_ADD_APPLICATION_ERROR, ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG, @@ -175,13 +192,16 @@ APPLICATION_INFO::TryCreateApplication(IHttpContext& pHttpContext, const ShimOpt } } - RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), m_pApplicationFactory, options, error)); + std::filesystem::path shadowCopyPath = HandleShadowCopy(options, pHttpContext); + + RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), shadowCopyPath, m_pApplicationFactory, options, error)); LOG_INFO(L"Creating handler application"); IAPPLICATION * newApplication; RETURN_IF_FAILED(m_pApplicationFactory->Execute( &m_pServer, &pHttpContext, + shadowCopyPath, &newApplication)); m_pApplication.reset(newApplication); @@ -206,19 +226,92 @@ APPLICATION_INFO::TryCreateHandler( return S_OK; } } + return S_FALSE; } VOID APPLICATION_INFO::ShutDownApplication(const bool fServerInitiated) { + IAPPLICATION* app = nullptr; + { + SRWExclusiveLock lock(m_applicationLock); + if (!m_pApplication) + { + return; + } + app = m_pApplication.get(); + } + + LOG_INFOF(L"Stopping application '%ls'", QueryApplicationInfoKey().c_str()); + app->Stop(fServerInitiated); + LOG_INFO(L"Setting app to null"); + SRWExclusiveLock lock(m_applicationLock); + LOG_INFO(L"lock acquired"); + + m_pApplication = nullptr; + m_pApplicationFactory = nullptr; +} - if (m_pApplication) +std::wstring +APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext) +{ + std::filesystem::path shadowCopyPath; + + if (options.QueryShadowCopyEnabled()) { - LOG_INFOF(L"Stopping application '%ls'", QueryApplicationInfoKey().c_str()); - m_pApplication->Stop(fServerInitiated); - m_pApplication = nullptr; - m_pApplicationFactory = nullptr; + shadowCopyPath = options.QueryShadowCopyDirectory(); + std::wstring physicalPath = pHttpContext.GetApplication()->GetApplicationPhysicalPath(); + + // Make shadow copy path absolute. + if (!shadowCopyPath.is_absolute()) + { + shadowCopyPath = std::filesystem::absolute(std::filesystem::path(physicalPath) / shadowCopyPath); + } + + // The shadow copy directory itself isn't copied to directly. + // Instead subdirectories with numerically increasing names are created. + // This is because on shutdown, the app itself will still have all dlls loaded, + // meaning we can't copy to the same subdirectory. Therefore, on shutdown, + // we create a directory that is one larger than the previous largest directory number. + auto directoryName = 0; + std::string directoryNameStr = "0"; + auto shadowCopyBaseDirectory = std::filesystem::directory_entry(shadowCopyPath); + if (!shadowCopyBaseDirectory.exists()) + { + CreateDirectory(shadowCopyBaseDirectory.path().wstring().c_str(), NULL); + } + + for (auto& entry : std::filesystem::directory_iterator(shadowCopyPath)) + { + if (entry.is_directory()) + { + try + { + std::string::size_type size; + int intFileName = std::stoi(entry.path().filename().string(), &size); + if (intFileName > directoryName) + { + directoryName = intFileName; + directoryNameStr = std::string(entry.path().string()); + } + } + catch (...) + { + OBSERVE_CAUGHT_EXCEPTION(); + // Ignore any folders that can't be converted to an int. + } + } + } + + shadowCopyPath = shadowCopyPath / std::filesystem::path(directoryNameStr); + HRESULT hr = Environment::CopyToDirectory(physicalPath, shadowCopyPath, options.QueryCleanShadowCopyDirectory(), shadowCopyBaseDirectory.path().parent_path()); + if (hr != S_OK) + { + return std::wstring(); + } } + + return shadowCopyPath; } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h index e16eae191337..c75c78660feb 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h @@ -66,6 +66,7 @@ class APPLICATION_INFO: NonCopyable return false; } + private: HRESULT @@ -79,6 +80,9 @@ class APPLICATION_INFO: NonCopyable HRESULT TryCreateApplication(IHttpContext& pHttpContext, const ShimOptions& options, ErrorContext& error); + std::wstring + HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext); + IHttpServer &m_pServer; HandlerResolver &m_handlerResolver; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp index d940b39302b7..6240892ccaa6 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp @@ -10,6 +10,7 @@ #include "EventLog.h" extern BOOL g_fInShutdown; +extern BOOL g_fInAppOfflineShutdown; // // Retrieves the application info from the application manager @@ -108,14 +109,33 @@ APPLICATION_MANAGER::RecycleApplicationFromManager( if (itr->second->ConfigurationPathApplies(configurationPath)) { applicationsToRecycle.emplace_back(itr->second); - itr = m_pApplicationInfoHash.erase(itr); + // Delete after shutting the application down to avoid creating + // another application info, which would just return app_offline. + if (m_handlerResolver.GetHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) + { + ++itr; + } + else + { + itr = m_pApplicationInfoHash.erase(itr); + } } else { ++itr; + } } + if (m_handlerResolver.GetHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) + { + // For detecting app_offline when the app_offline file isn't present. + // Normally, app_offline state is independent of application + // (Just checks for app_offline file). + // For shadow copying, we need some other indication that the app is offline. + g_fInAppOfflineShutdown = true; + } + // All applications were unloaded reset handler resolver validation logic if (m_pApplicationInfoHash.empty()) { @@ -155,6 +175,26 @@ APPLICATION_MANAGER::RecycleApplicationFromManager( } } } + { + SRWExclusiveLock lock(m_srwLock); + const std::wstring configurationPath = pszApplicationId; + + // Remove apps after calling shutdown on each of them + // This is exclusive to in-process, as the shutdown of an inprocess app recycles + // the entire worker process. + auto itr = m_pApplicationInfoHash.begin(); + while (itr != m_pApplicationInfoHash.end()) + { + if (itr->second != nullptr && itr->second->ConfigurationPathApplies(configurationPath)) + { + itr = m_pApplicationInfoHash.erase(itr); + } + else + { + ++itr; + } + } + } } CATCH_RETURN() @@ -172,6 +212,7 @@ APPLICATION_MANAGER::ShutDown() // However, it is possible to receive multiple OnGlobalStopListening events // Protect against this by checking if we already shut down. g_fInShutdown = TRUE; + g_fInAppOfflineShutdown = true; // During shutdown we lock until we delete the application SRWExclusiveLock lock(m_srwLock); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp index b3a98f0c7e3e..acf90e07c622 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp @@ -17,6 +17,7 @@ DECLARE_DEBUG_PRINT_OBJECT("aspnetcorev2.dll"); HANDLE g_hEventLog = nullptr; BOOL g_fRecycleProcessCalled = FALSE; BOOL g_fInShutdown = FALSE; +BOOL g_fInAppOfflineShutdown = FALSE; HINSTANCE g_hServerModule; VOID diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp index e3a435bc9b87..162c0fea907b 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp @@ -100,7 +100,12 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( *pHttpContext, m_pApplicationInfo)); - FINISHED_IF_FAILED(m_pApplicationInfo->CreateHandler(*pHttpContext, m_pHandler)); + FINISHED_IF_FAILED(hr = m_pApplicationInfo->CreateHandler(*pHttpContext, m_pHandler)); + + if (m_pHandler == nullptr) + { + FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)); + } SetupDisconnectHandler(pHttpContext); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp index fb63cdb20c99..b4feb060bda9 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp @@ -151,3 +151,87 @@ bool Environment::IsRunning64BitProcess() GetNativeSystemInfo(&systemInfo); return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; } + +HRESULT Environment::CopyToDirectory(std::wstring source, std::filesystem::path destination, bool cleanDest, std::filesystem::path directoryToIgnore) +{ + if (cleanDest && std::filesystem::exists(destination)) + { + std::filesystem::remove_all(destination); + } + + Environment::CopyToDirectoryInner(source, destination, directoryToIgnore); + return S_OK; +} + +void Environment::CopyToDirectoryInner(const std::filesystem::path& source, const std::filesystem::path& destination, const std::filesystem::path& directoryToIgnore) +{ + auto destinationDirEntry = std::filesystem::directory_entry(destination); + if (!destinationDirEntry.exists()) + { + CreateDirectory(destination.wstring().c_str(), NULL); + } + + for (auto& path : std::filesystem::directory_iterator(source)) + { + if (path.is_regular_file()) + { + auto sourceFile = path.path().filename(); + auto destinationPath = (destination / sourceFile); + + if (std::filesystem::directory_entry(destinationPath).exists()) + { + auto sourceFileTime = std::filesystem::last_write_time(path); + auto destinationFileTime = std::filesystem::last_write_time(destinationPath); + if (sourceFileTime <= destinationFileTime) // file write time is the same + { + continue; + } + } + + CopyFile(path.path().wstring().c_str(), destinationPath.wstring().c_str(), FALSE); + } + else if (path.is_directory()) + { + auto sourceInnerDirectory = std::filesystem::directory_entry(path); + if (sourceInnerDirectory.path() != directoryToIgnore) + { + CopyToDirectoryInner(path.path(), destination / path.path().filename(), directoryToIgnore); + } + } + } +} + +bool Environment::CheckUpToDate(std::wstring source, std::filesystem::path destination, const std::wstring& extension, const std::filesystem::path& directoryToIgnore) +{ + for (auto& path : std::filesystem::directory_iterator(source)) + { + if (path.is_regular_file() + && path.path().has_extension() + && path.path().filename().extension().wstring() == extension) + { + auto sourceFile = path.path().filename(); + auto destinationPath = (destination / sourceFile); + + if (std::filesystem::directory_entry(destinationPath).exists()) + { + auto originalFileTime = std::filesystem::last_write_time(path); + auto destFileTime = std::filesystem::last_write_time(destinationPath); + if (originalFileTime > destFileTime) // file write time is the same + { + return false; + } + } + + CopyFile(path.path().wstring().c_str(), destinationPath.wstring().c_str(), FALSE); + } + else if (path.is_directory()) + { + auto sourceInnerDirectory = std::filesystem::directory_entry(path); + if (sourceInnerDirectory.path() != directoryToIgnore) + { + CheckUpToDate(destination / path.path().filename(), path.path(), extension, directoryToIgnore); + } + } + } + return true; +} diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h index 4f2611ef71f2..8f6992535087 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h @@ -22,5 +22,12 @@ class Environment std::wstring GetDllDirectoryValue(); static bool IsRunning64BitProcess(); + static + HRESULT CopyToDirectory(std::wstring source, std::filesystem::path destination, bool cleanDest, std::filesystem::path directoryToIgnore); + static + bool CheckUpToDate(std::wstring source, std::filesystem::path destination, const std::wstring& extension, const std::filesystem::path& directoryToIgnore); +private: + static + void CopyToDirectoryInner(const std::filesystem::path& source_folder, const std::filesystem::path& target_folder, const std::filesystem::path& directoryToIgnore); }; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp index cf2395e7b585..9353e427cf77 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp @@ -11,6 +11,7 @@ HRESULT PollingAppOfflineApplication::TryCreateHandler(_In_ IHttpContext* pHttpContext, _Outptr_result_maybenull_ IREQUEST_HANDLER** pRequestHandler) { CheckAppOffline(); + return LOG_IF_FAILED(APPLICATION::TryCreateHandler(pHttpContext, pRequestHandler)); } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/application.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/application.h index ae30844330cc..742ee81a97fd 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/application.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/application.h @@ -129,11 +129,11 @@ class APPLICATION : public IAPPLICATION SRWLOCK m_stopLock{}; SRWLOCK m_dataLock {}; bool m_fStopCalled; + std::wstring m_applicationPhysicalPath; private: mutable LONG m_cRefs; - std::wstring m_applicationPhysicalPath; std::wstring m_applicationVirtualPath; std::wstring m_applicationConfigPath; std::wstring m_applicationId; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc index 20d04bd56bf6..ace6bb1b98be 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc @@ -207,6 +207,12 @@ Language=English %1 . +Messageid=1037 +SymbolicName=ASPNETCORE_EVENT_RECYCLE_FILECHANGE +Language=English +%1 +. + ; ;#endif // _ASPNETCORE_MODULE_MSG_H_ diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/resources.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/resources.h index 9535d1c0cbd8..a561fa50c458 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/resources.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/resources.h @@ -33,6 +33,7 @@ #define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG L"Application '%s' with physical root '%s' has exited from Program.Main with exit code = '%d'. First 30KB characters of captured stdout and stderr logs:\r\n%s" #define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' has exited from Program.Main with exit code = '%d'. Please check the stderr logs for more information." #define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG L"Application '%s' was recycled after detecting app_offline.htm." +#define ASPNETCORE_EVENT_RECYCLE_FILECHANGE_MSG L"Application '%s' was recycled after detecting file change in application directory." #define ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR_MSG L"Failed to monitor app_offline.htm for application '%s', ErrorCode '0x%x'. " #define ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG L"Application '%s' was recycled due to configuration change" #define ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG L"Failed to recycle application after a configuration change at '%s'. Recycling worker process." diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp index fb9fe8a01502..21f90f18c621 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp @@ -5,8 +5,9 @@ InProcessApplicationBase::InProcessApplicationBase( IHttpServer& pHttpServer, - IHttpApplication& pHttpApplication) - : AppOfflineTrackingApplication(pHttpApplication), + IHttpApplication& pHttpApplication, + const std::wstring& shadowCopyDirectory) + : AppOfflineTrackingApplication(pHttpApplication, shadowCopyDirectory), m_fRecycleCalled(FALSE), m_pHttpServer(pHttpServer) { @@ -29,6 +30,7 @@ InProcessApplicationBase::StopInternal(bool fServerInitiated) // We don't actually handle any shutdown logic here. // Instead, we notify IIS that the process needs to be recycled, which will call // ApplicationManager->Shutdown(). This will call shutdown on the application. + LOG_INFO(L"AspNetCore InProcess Recycle Process on Demand"); m_pHttpServer.RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); } else diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h index d8f141648c9a..61656b487037 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h @@ -11,7 +11,8 @@ class InProcessApplicationBase : public AppOfflineTrackingApplication InProcessApplicationBase( IHttpServer& pHttpServer, - IHttpApplication& pHttpApplication); + IHttpApplication& pHttpApplication, + const std::wstring& shadowCoypDirectory); ~InProcessApplicationBase() = default; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h index 171ad4a2efff..5af0a43aac12 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h @@ -32,7 +32,7 @@ class ShuttingDownApplication : public InProcessApplicationBase { public: ShuttingDownApplication(IHttpServer& pHttpServer, IHttpApplication& pHttpApplication) - : InProcessApplicationBase(pHttpServer, pHttpApplication) + : InProcessApplicationBase(pHttpServer, pHttpApplication, /* shadowCopyDirectory */ std::wstring()) { } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h index d3e50e620fa8..3f1dc36ff1f9 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h @@ -25,7 +25,7 @@ class StartupExceptionApplication : public InProcessApplicationBase m_statusCode(statusCode), m_subStatusCode(subStatusCode), m_statusText(std::move(statusText)), - InProcessApplicationBase(pServer, pApplication) + InProcessApplicationBase(pServer, pApplication, /* shadowCopyDirectory */ std::wstring()) { } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 8643f8089aae..e54580f22766 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -22,7 +22,9 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( std::unique_ptr pConfig, APPLICATION_PARAMETER* pParameters, DWORD nParameters) : - InProcessApplicationBase(pHttpServer, pApplication), + InProcessApplicationBase(pHttpServer, + pApplication, + FindParameter("ShadowCopyDirectory", pParameters, nParameters)), m_Initialized(false), m_blockManagedCallbacks(true), m_waitForShutdown(true), @@ -48,6 +50,9 @@ IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION() VOID IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated) { + // Stop app offline tracking before shutting down CLR. + // This is to help with shadow copy scenario where the app is shutting down. + AppOfflineTrackingApplication::StopInternal(fServerInitiated); StopClr(); InProcessApplicationBase::StopInternal(fServerInitiated); } @@ -92,6 +97,12 @@ IN_PROCESS_APPLICATION::StopClr() m_workerThread.join(); } + if (m_folderCleanupThread.joinable()) + { + // Worker thread would wait for clr to finish and log error if required + m_folderCleanupThread.join(); + } + s_Application = nullptr; } @@ -146,6 +157,29 @@ IN_PROCESS_APPLICATION::LoadManagedApplication(ErrorContext& errorContext) LOG_INFO(L"Waiting for initialization"); + THROW_IF_FAILED(StartMonitoringAppOffline()); + + if (!m_shadowCopyDirectory.empty()) + { + if (!Environment::CheckUpToDate(QueryApplicationPhysicalPath(), m_shadowCopyDirectory, L".dll", std::filesystem::path(m_shadowCopyDirectory).parent_path())) + { + Stop(/* fServerInitiated */false); + throw InvalidOperationException(L"File changed between copy and start of application, restarting."); + } + // Cleanup other directories that haven't been removed. + m_folderCleanupThread = std::thread([](std::wstring shadowCopyDir) + { + auto parentDir = std::filesystem::path(shadowCopyDir).parent_path(); + for (auto& p : std::filesystem::directory_iterator(parentDir)) + { + if (p.path() != shadowCopyDir) + { + std::filesystem::remove_all(p.path()); + } + } + }, m_shadowCopyDirectory); + } + m_workerThread = std::thread([](std::unique_ptr application) { LOG_INFO(L"Starting in-process worker thread"); @@ -188,8 +222,6 @@ IN_PROCESS_APPLICATION::LoadManagedApplication(ErrorContext& errorContext) throw InvalidOperationException(format(L"CLR worker thread exited prematurely")); } - THROW_IF_FAILED(StartMonitoringAppOffline()); - return S_OK; } @@ -209,7 +241,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication() THROW_IF_FAILED(HostFxrResolutionResult::Create( m_dotnetExeKnownLocation, m_pConfig->QueryProcessPath(), - QueryApplicationPhysicalPath(), + m_shadowCopyDirectory.empty() ? QueryApplicationPhysicalPath() : m_shadowCopyDirectory, m_pConfig->QueryArguments(), errorContext, hostFxrResolutionResult @@ -577,7 +609,6 @@ IN_PROCESS_APPLICATION::CreateHandler( try { SRWSharedLock dataLock(m_dataLock); - DBG_ASSERT(!m_fStopCalled); m_requestCount++; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h index 4eda1fd7dadc..10890168c182 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h @@ -145,6 +145,8 @@ class IN_PROCESS_APPLICATION : public InProcessApplicationBase std::thread m_clrThread; // Thread tracking the CLR thread, this one is always joined on shutdown std::thread m_workerThread; + // Thread for cleaning up existing shadow copy folders + std::thread m_folderCleanupThread; // The event that gets triggered when managed initialization is complete HandleWrapper m_pInitializeEvent; // The event that gets triggered when worker thread should exit diff --git a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp index 7bd0a5fa83bf..c471262d3c20 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp @@ -2,14 +2,13 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #include "outprocessapplication.h" - #include "SRWExclusiveLock.h" #include "exceptions.h" OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION( IHttpApplication& pApplication, std::unique_ptr pConfig) : - AppOfflineTrackingApplication(pApplication), + AppOfflineTrackingApplication(pApplication, /* shadowCopyDirectory */ std::wstring()), m_fWebSocketSupported(WEBSOCKET_STATUS::WEBSOCKET_UNKNOWN), m_pConfig(std::move(pConfig)) { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp index a946e7c2dc5a..7c02e4aeb8c1 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp @@ -25,13 +25,13 @@ HRESULT AppOfflineTrackingApplication::StartMonitoringAppOffline() void AppOfflineTrackingApplication::StopInternal(bool fServerInitiated) { - APPLICATION::StopInternal(fServerInitiated); - if (m_fileWatcher) { m_fileWatcher->StopMonitor(); m_fileWatcher = nullptr; } + + APPLICATION::StopInternal(fServerInitiated); } HRESULT AppOfflineTrackingApplication::StartMonitoringAppOflineImpl() @@ -44,6 +44,7 @@ HRESULT AppOfflineTrackingApplication::StartMonitoringAppOflineImpl() m_fileWatcher = std::make_unique(); RETURN_IF_FAILED(m_fileWatcher->Create(m_applicationPath.c_str(), L"app_offline.htm", + m_shadowCopyDirectory, this)); return S_OK; @@ -56,11 +57,22 @@ void AppOfflineTrackingApplication::OnAppOffline() return; } - LOG_INFOF(L"Received app_offline notification in application '%ls'", m_applicationPath.c_str()); - EventLog::Info( - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, - m_applicationPath.c_str()); + if (m_detectedAppOffline) + { + LOG_INFOF(L"Received app_offline notification in application '%ls'", m_applicationPath.c_str()); + EventLog::Info( + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, + m_applicationPath.c_str()); + } + else + { + LOG_INFOF(L"Received file change notification in application '%ls'", m_applicationPath.c_str()); + EventLog::Info( + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, + ASPNETCORE_EVENT_RECYCLE_FILECHANGE_MSG, + m_applicationPath.c_str()); + } Stop(/*fServerInitiated*/ false); } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h index b504a730fd62..4cc655a35c41 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h @@ -11,12 +11,13 @@ class AppOfflineTrackingApplication: public APPLICATION { public: - AppOfflineTrackingApplication(const IHttpApplication& application) + AppOfflineTrackingApplication(const IHttpApplication& application, const std::wstring& shadowCopyDirectory) : APPLICATION(application), m_applicationPath(application.GetApplicationPhysicalPath()), m_fileWatcher(nullptr), m_fAppOfflineProcessed(false) { + m_shadowCopyDirectory = shadowCopyDirectory; } ~AppOfflineTrackingApplication() override @@ -37,6 +38,9 @@ class AppOfflineTrackingApplication: public APPLICATION VOID OnAppOffline(); + // TODO protected + bool m_detectedAppOffline; + std::wstring m_shadowCopyDirectory; private: HRESULT StartMonitoringAppOflineImpl(); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 9d17f95b37c3..160140c60d74 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -6,21 +6,32 @@ #include "debugutil.h" #include "AppOfflineTrackingApplication.h" #include "exceptions.h" +#include FILE_WATCHER::FILE_WATCHER() : m_hCompletionPort(NULL), m_hChangeNotificationThread(NULL), - m_fThreadExit(FALSE) + m_fThreadExit(FALSE), + m_fShadowCopyEnabled(FALSE), + m_copied(false) { + m_pDoneCopyEvent = CreateEvent( + nullptr, // default security attributes + TRUE, // manual reset event + FALSE, // not set + nullptr); // name } FILE_WATCHER::~FILE_WATCHER() { StopMonitor(); + WaitForMonitor(20); // wait for 1 second total +} +void FILE_WATCHER::WaitForMonitor(DWORD dwRetryCounter) +{ if (m_hChangeNotificationThread != NULL) { - DWORD dwRetryCounter = 20; // totally wait for 1s DWORD dwExitCode = STILL_ACTIVE; while (!m_fThreadExit && dwRetryCounter > 0) @@ -54,9 +65,12 @@ HRESULT FILE_WATCHER::Create( _In_ PCWSTR pszDirectoryToMonitor, _In_ PCWSTR pszFileNameToMonitor, - _In_ AppOfflineTrackingApplication *pApplication + _In_ std::wstring shadowCopyPath, + _In_ AppOfflineTrackingApplication* pApplication ) { + m_shadowCopyPath = shadowCopyPath; + m_fShadowCopyEnabled = !shadowCopyPath.empty(); RETURN_LAST_ERROR_IF_NULL(m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)); @@ -82,6 +96,8 @@ FILE_WATCHER::Create( RETURN_IF_FAILED(_strFullName.Append(_strDirectoryName)); RETURN_IF_FAILED(_strFullName.Append(_strFileName)); + // TODO I'm concerned about how detecting a dll change will work if it too late + // // Resize change buffer to something "reasonable" // @@ -130,10 +146,10 @@ Win32 error --*/ { - FILE_WATCHER * pFileMonitor; + FILE_WATCHER* pFileMonitor; BOOL fSuccess = FALSE; DWORD cbCompletion = 0; - OVERLAPPED * pOverlapped = NULL; + OVERLAPPED* pOverlapped = NULL; DWORD dwErrorStatus; ULONG_PTR completionKey; @@ -177,7 +193,16 @@ Win32 error pFileMonitor->m_fThreadExit = TRUE; + // TODO check instead for if a dll was changed here + if (pFileMonitor->m_fShadowCopyEnabled) + { + // Cancel the timer to avoid it calling copy. + pFileMonitor->m_Timer.CancelTimer(); + FILE_WATCHER::CopyAndShutdown(pFileMonitor); + } + LOG_INFO(L"Stopping file watcher thread"); + ExitThread(0); } @@ -204,7 +229,8 @@ HRESULT --*/ { - BOOL fFileChanged = FALSE; + BOOL fAppOfflineChanged = FALSE; + BOOL fDllChanged = FALSE; // When directory handle is closed then HandleChangeCompletion // happens with cbCompletion = 0 and dwCompletionStatus = 0 @@ -226,7 +252,7 @@ HRESULT // if (cbCompletion == 0) { - fFileChanged = TRUE; + fAppOfflineChanged = TRUE; } else { @@ -242,9 +268,22 @@ HRESULT _strFileName.QueryStr(), pNotificationInfo->FileNameLength / sizeof(WCHAR)) == 0) { - fFileChanged = TRUE; + fAppOfflineChanged = TRUE; + auto app = _pApplication.get(); + app->m_detectedAppOffline = true; break; } + + // + // Look for changes to dlls when shadow copying is enabled. + // + std::wstring notification(pNotificationInfo->FileName, pNotificationInfo->FileNameLength / sizeof(WCHAR)); + std::filesystem::path notificationPath(notification); + if (m_fShadowCopyEnabled && notificationPath.extension().compare(L".dll") == 0) + { + fDllChanged = TRUE; + } + // // Advance to next notification // @@ -261,16 +300,95 @@ HRESULT } } - if (fFileChanged && !_lStopMonitorCalled) + if (fAppOfflineChanged && !_lStopMonitorCalled) { // Reference application before _pApplication->ReferenceApplication(); RETURN_LAST_ERROR_IF(!QueueUserWorkItem(RunNotificationCallback, _pApplication.get(), WT_EXECUTEDEFAULT)); } + if (fDllChanged && m_fShadowCopyEnabled && !_lStopMonitorCalled) + { + // Reset timer for dll checks + LOG_INFO(L"Detected dll change, resetting timer callback which will eventually trigger shutdown."); + m_Timer.CancelTimer(); + m_Timer.InitializeTimer(FILE_WATCHER::TimerCallback, this, 5000, 3000); + } + return S_OK; } + +VOID +CALLBACK +FILE_WATCHER::TimerCallback( + _In_ PTP_CALLBACK_INSTANCE Instance, + _In_ PVOID Context, + _In_ PTP_TIMER Timer +) +{ + Instance; + Timer; + CopyAndShutdown((FILE_WATCHER*)Context); +} + +DWORD WINAPI FILE_WATCHER::CopyAndShutdown(LPVOID arg) +{ + + auto watcher = (FILE_WATCHER*)arg; + + // Only copy and shutdown once + SRWExclusiveLock lock(watcher->m_copyLock); + if (watcher->m_copied) + { + return 0; + } + + watcher->m_copied = true; + + LOG_INFO(L"Starting copy on shutdown in filewatcher, creating directory."); + + auto directoryNameInt = 0; + auto currentShadowCopyDirectory = std::filesystem::path(watcher->m_shadowCopyPath); + auto parentDirectory = currentShadowCopyDirectory.parent_path(); + try + { + directoryNameInt = std::stoi(currentShadowCopyDirectory.filename().string()); + } + catch (...) + { + OBSERVE_CAUGHT_EXCEPTION(); + return 0; + } + + // Add one to the directory we want to copy to. + directoryNameInt++; + auto destination = parentDirectory / std::to_wstring(directoryNameInt); + + LOG_INFOF(L"Copying new shadow copy directory to %ls.", destination.wstring().c_str()); + + // Copy contents before shutdown + try + { + Environment::CopyToDirectory(watcher->_strDirectoryName.QueryStr(), destination, false, parentDirectory); + } + catch (...) + { + OBSERVE_CAUGHT_EXCEPTION(); + return 0; + } + + LOG_INFOF(L"Finished copy on shutdown to %ls.", destination.wstring().c_str()); + + SetEvent(watcher->m_pDoneCopyEvent); + + // reference application before callback (same thing we do with app_offline). + watcher->_pApplication->ReferenceApplication(); + QueueUserWorkItem(RunNotificationCallback, watcher->_pApplication.get(), WT_EXECUTEDEFAULT); + + return 0; +} + DWORD WINAPI FILE_WATCHER::RunNotificationCallback( @@ -279,7 +397,6 @@ FILE_WATCHER::RunNotificationCallback( { // Recapture application instance into unique_ptr auto pApplication = std::unique_ptr(static_cast(pvArg)); - DBG_ASSERT(pFileMonitor != NULL); pApplication->OnAppOffline(); return 0; @@ -319,9 +436,23 @@ FILE_WATCHER::StopMonitor() // we know that HandleChangeCompletion() call // can be ignored // - InterlockedExchange(&_lStopMonitorCalled, 1); + if (InterlockedExchange(&_lStopMonitorCalled, 1) == 1) + { + return; + } + + LOG_INFO(L"Stopping file watching."); + // signal the file watch thread to exit PostQueuedCompletionStatus(m_hCompletionPort, 0, FILE_WATCHER_SHUTDOWN_KEY, NULL); + WaitForMonitor(200); + + if (m_fShadowCopyEnabled && m_pDoneCopyEvent != nullptr) + { + // If we are shadow copying, wait for the copying to finish. + WaitForSingleObject(m_pDoneCopyEvent, 30000); + } + // Release application reference _pApplication.reset(nullptr); } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h index 46ea74453322..e5a963d05e55 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -8,6 +8,8 @@ #include #include "iapplication.h" #include "HandleWrapper.h" +#include "Environment.h" +#include #define FILE_WATCHER_SHUTDOWN_KEY (ULONG_PTR)(-1) #define FILE_WATCHER_ENTRY_BUFFER_SIZE 4096 @@ -22,9 +24,15 @@ class FILE_WATCHER{ ~FILE_WATCHER(); + void WaitForMonitor(DWORD dwRetryCounter); + HRESULT Create( _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + + _In_ std::wstring shadowCopyPath, + _In_ AppOfflineTrackingApplication *pApplication ); @@ -36,6 +44,14 @@ class FILE_WATCHER{ DWORD WINAPI RunNotificationCallback(LPVOID); + static + VOID + WINAPI TimerCallback(_In_ PTP_CALLBACK_INSTANCE Instance, + _In_ PVOID Context, + _In_ PTP_TIMER Timer); + + static DWORD WINAPI CopyAndShutdown(LPVOID); + HRESULT HandleChangeCompletion(DWORD cbCompletion); HRESULT Monitor(); @@ -45,13 +61,19 @@ class FILE_WATCHER{ HandleWrapper m_hCompletionPort; HandleWrapper m_hChangeNotificationThread; HandleWrapper _hDirectory; + HandleWrapper m_pDoneCopyEvent; volatile BOOL m_fThreadExit; + STTIMER m_Timer; + SRWLOCK m_copyLock{}; + BOOL m_copied; BUFFER _buffDirectoryChanges; STRU _strFileName; STRU _strDirectoryName; STRU _strFullName; LONG _lStopMonitorCalled {}; + bool m_fShadowCopyEnabled; + std::wstring m_shadowCopyPath; OVERLAPPED _overlapped; std::unique_ptr _pApplication; }; diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/EventLogHelpers.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/EventLogHelpers.cs index 9332b5deb61c..744ea078193c 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/EventLogHelpers.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/EventLogHelpers.cs @@ -167,6 +167,11 @@ public static string InProcessShutdown() return "Application 'MACHINE/WEBROOT/APPHOST/.*?' has shutdown."; } + public static string ShutdownFileChange(IISDeploymentResult deploymentResult) + { + return $"Application '{EscapedContentRoot(deploymentResult)}' was recycled after detecting file change in application directory."; + } + public static string InProcessFailedToStop(IISDeploymentResult deploymentResult, string reason) { return "Failed to gracefully shutdown application 'MACHINE/WEBROOT/APPHOST/.*?'."; diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ShadowCopyTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ShadowCopyTests.cs new file mode 100644 index 000000000000..a02e96184617 --- /dev/null +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ShadowCopyTests.cs @@ -0,0 +1,261 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class ShadowCopyTests : IISFunctionalTestBase + { + public ShadowCopyTests(PublishedSitesFixture fixture) : base(fixture) + { + } + + [ConditionalFact] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_20H2, SkipReason = "Shutdown hangs https://github.com/dotnet/aspnetcore/issues/25107")] + public async Task ShadowCopyDoesNotLockFiles() + { + using var directory = TempDirectory.Create(); + var deploymentParameters = Fixture.GetBaseDeploymentParameters(); + deploymentParameters.HandlerSettings["enableShadowCopy"] = "true"; + deploymentParameters.HandlerSettings["shadowCopyDirectory"] = directory.DirectoryPath; + + var deploymentResult = await DeployAsync(deploymentParameters); + var response = await deploymentResult.HttpClient.GetAsync("Wow!"); + + Assert.True(response.IsSuccessStatusCode); + var directoryInfo = new DirectoryInfo(deploymentResult.ContentRoot); + + // Verify that we can delete all files in the content root (nothing locked) + foreach (var fileInfo in directoryInfo.GetFiles()) + { + fileInfo.Delete(); + } + + foreach (var dirInfo in directoryInfo.GetDirectories()) + { + dirInfo.Delete(recursive: true); + } + } + + [ConditionalFact] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_20H2, SkipReason = "Shutdown hangs https://github.com/dotnet/aspnetcore/issues/25107")] + public async Task ShadowCopyRelativeInSameDirectoryWorks() + { + var directoryName = Path.GetRandomFileName(); + var deploymentParameters = Fixture.GetBaseDeploymentParameters(); + deploymentParameters.HandlerSettings["enableShadowCopy"] = "true"; + deploymentParameters.HandlerSettings["shadowCopyDirectory"] = directoryName; + + var deploymentResult = await DeployAsync(deploymentParameters); + var response = await deploymentResult.HttpClient.GetAsync("Wow!"); + + Assert.True(response.IsSuccessStatusCode); + var directoryInfo = new DirectoryInfo(deploymentResult.ContentRoot); + + // Verify that we can delete all files in the content root (nothing locked) + foreach (var fileInfo in directoryInfo.GetFiles()) + { + fileInfo.Delete(); + } + + var tempDirectoryPath = Path.Combine(deploymentResult.ContentRoot, directoryName); + foreach (var dirInfo in directoryInfo.GetDirectories()) + { + if (!tempDirectoryPath.Equals(dirInfo.FullName)) + { + dirInfo.Delete(recursive: true); + } + } + } + + [ConditionalFact] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_20H2, SkipReason = "Shutdown hangs https://github.com/dotnet/aspnetcore/issues/25107")] + public async Task ShadowCopyRelativeOutsideDirectoryWorks() + { + using var directory = TempDirectory.Create(); + var deploymentParameters = Fixture.GetBaseDeploymentParameters(); + deploymentParameters.HandlerSettings["enableShadowCopy"] = "true"; + deploymentParameters.HandlerSettings["shadowCopyDirectory"] = $"..\\{directory.DirectoryInfo.Name}"; + deploymentParameters.ApplicationPath = directory.DirectoryPath; + + var deploymentResult = await DeployAsync(deploymentParameters); + var response = await deploymentResult.HttpClient.GetAsync("Wow!"); + + // Check if directory can be deleted. + // Can't delete the folder but can delete all content in it. + + Assert.True(response.IsSuccessStatusCode); + var directoryInfo = new DirectoryInfo(deploymentResult.ContentRoot); + + // Verify that we can delete all files in the content root (nothing locked) + foreach (var fileInfo in directoryInfo.GetFiles()) + { + fileInfo.Delete(); + } + + foreach (var dirInfo in directoryInfo.GetDirectories()) + { + dirInfo.Delete(recursive: true); + } + + StopServer(); + deploymentResult.AssertWorkerProcessStop(); + } + + [ConditionalFact] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_20H2, SkipReason = "Shutdown hangs https://github.com/dotnet/aspnetcore/issues/25107")] + public async Task ShadowCopySingleFileChangedWorks() + { + using var directory = TempDirectory.Create(); + var deploymentParameters = Fixture.GetBaseDeploymentParameters(); + deploymentParameters.HandlerSettings["enableShadowCopy"] = "true"; + deploymentParameters.HandlerSettings["shadowCopyDirectory"] = directory.DirectoryPath; + + var deploymentResult = await DeployAsync(deploymentParameters); + + DirectoryCopy(deploymentResult.ContentRoot, directory.DirectoryPath, copySubDirs: true); + + var response = await deploymentResult.HttpClient.GetAsync("Wow!"); + Assert.True(response.IsSuccessStatusCode); + // Rewrite file + var dirInfo = new DirectoryInfo(deploymentResult.ContentRoot); + + string dllPath = ""; + foreach (var file in dirInfo.EnumerateFiles()) + { + if (file.Extension == ".dll") + { + dllPath = file.FullName; + break; + } + } + var fileContents = File.ReadAllBytes(dllPath); + File.WriteAllBytes(dllPath, fileContents); + + deploymentResult.AssertWorkerProcessStop(); + + response = await deploymentResult.HttpClient.GetAsync("Wow!"); + Assert.True(response.IsSuccessStatusCode); + } + + [ConditionalFact] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_20H2, SkipReason = "Shutdown hangs https://github.com/dotnet/aspnetcore/issues/25107")] + public async Task ShadowCopyE2EWorksWithFolderPresent() + { + using var directory = TempDirectory.Create(); + var deploymentParameters = Fixture.GetBaseDeploymentParameters(); + deploymentParameters.HandlerSettings["enableShadowCopy"] = "true"; + deploymentParameters.HandlerSettings["shadowCopyDirectory"] = directory.DirectoryPath; + var deploymentResult = await DeployAsync(deploymentParameters); + + DirectoryCopy(deploymentResult.ContentRoot, Path.Combine(directory.DirectoryPath, "0"), copySubDirs: true); + + var response = await deploymentResult.HttpClient.GetAsync("Wow!"); + Assert.True(response.IsSuccessStatusCode); + + using var secondTempDir = TempDirectory.Create(); + + // copy back and forth to cause file change notifications. + DirectoryCopy(deploymentResult.ContentRoot, secondTempDir.DirectoryPath, copySubDirs: true); + DirectoryCopy(secondTempDir.DirectoryPath, deploymentResult.ContentRoot, copySubDirs: true); + + deploymentResult.AssertWorkerProcessStop(); + response = await deploymentResult.HttpClient.GetAsync("Wow!"); + Assert.True(response.IsSuccessStatusCode); + } + + public class TempDirectory : IDisposable + { + public static TempDirectory Create() + { + var directoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + var directoryInfo = Directory.CreateDirectory(directoryPath); + return new TempDirectory(directoryInfo); + } + + public TempDirectory(DirectoryInfo directoryInfo) + { + DirectoryInfo = directoryInfo; + + DirectoryPath = directoryInfo.FullName; + } + + public string DirectoryPath { get; } + public DirectoryInfo DirectoryInfo { get; } + + public void Dispose() + { + DeleteDirectory(DirectoryPath); + } + + private static void DeleteDirectory(string directoryPath) + { + foreach (var subDirectoryPath in Directory.EnumerateDirectories(directoryPath)) + { + DeleteDirectory(subDirectoryPath); + } + + try + { + foreach (var filePath in Directory.EnumerateFiles(directoryPath)) + { + var fileInfo = new FileInfo(filePath) + { + Attributes = FileAttributes.Normal + }; + fileInfo.Delete(); + } + Directory.Delete(directoryPath); + } + catch (Exception e) + { + Console.WriteLine($@"Failed to delete directory {directoryPath}: {e.Message}"); + } + } + } + + // copied from https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories + private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs) + { + // Get the subdirectories for the specified directory. + DirectoryInfo dir = new DirectoryInfo(sourceDirName); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException( + "Source directory does not exist or could not be found: " + + sourceDirName); + } + + DirectoryInfo[] dirs = dir.GetDirectories(); + + // If the destination directory doesn't exist, create it. + Directory.CreateDirectory(destDirName); + + // Get the files in the directory and copy them to the new location. + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + string tempPath = Path.Combine(destDirName, file.Name); + file.CopyTo(tempPath, true); + } + + // If copying subdirectories, copy them and their contents to new location. + if (copySubDirs) + { + foreach (DirectoryInfo subdir in dirs) + { + string tempPath = Path.Combine(destDirName, subdir.Name); + DirectoryCopy(subdir.FullName, tempPath, copySubDirs); + } + } + } + } +} diff --git a/src/Servers/IIS/IISIntegration.slnf b/src/Servers/IIS/IISIntegration.slnf index e8ce42f08921..f5f7ddf1ec9a 100644 --- a/src/Servers/IIS/IISIntegration.slnf +++ b/src/Servers/IIS/IISIntegration.slnf @@ -36,6 +36,7 @@ "src\\Servers\\IIS\\IIS\\perf\\Microbenchmarks\\IIS.Microbenchmarks.csproj", "src\\Servers\\IIS\\IIS\\samples\\NativeIISSample\\NativeIISSample.csproj", "src\\Servers\\IIS\\IIS\\src\\Microsoft.AspNetCore.Server.IIS.csproj", + "src\\Servers\\IIS\\IIS\\samples\\NativeIISSample\\NativeIISSample.csproj", "src\\Servers\\IIS\\IIS\\test\\IIS.FunctionalTests\\IIS.FunctionalTests.csproj", "src\\Servers\\IIS\\IIS\\test\\IIS.NewHandler.FunctionalTests\\IIS.NewHandler.FunctionalTests.csproj", "src\\Servers\\IIS\\IIS\\test\\IIS.NewShim.FunctionalTests\\IIS.NewShim.FunctionalTests.csproj", diff --git a/src/Servers/IIS/copyToIIS.ps1 b/src/Servers/IIS/copyToIIS.ps1 new file mode 100644 index 000000000000..696932e5ded4 --- /dev/null +++ b/src/Servers/IIS/copyToIIS.ps1 @@ -0,0 +1,4 @@ +net stop was /Y +.\build.cmd +cp "C:\Users\jukotali\code\aspnetcore\artifacts\bin\AspNetCoreModuleShim\x64\Debug\*" "C:\Program Files\IIS\Asp.Net Core Module\V2\" -Force +net start w3svc /Y \ No newline at end of file From a6ba6cc6b99058047539c0055196ef6ffff14a86 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 17 Feb 2021 14:26:12 -0800 Subject: [PATCH 02/15] Set app context --- src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h | 1 + .../InProcessRequestHandler/inprocessapplication.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h index a6cf2bb889ac..5bb696da311c 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h @@ -17,6 +17,7 @@ struct hostfxr_initialize_parameters #define DOTNETCORE_STARTUP_HOOK L"STARTUP_HOOKS" #define DOTNETCORE_USE_ENTRYPOINT_FILTER L"USE_ENTRYPOINT_FILTER" #define DOTNETCORE_STACK_SIZE L"DEFAULT_STACK_SIZE" +#define APP_CONTEXT_BASE_DIRECTORY L"APP_CONTEXT_BASE_DIRECTORY" #define ASPNETCORE_STARTUP_ASSEMBLY L"Microsoft.AspNetCore.Server.IIS" typedef INT(*hostfxr_get_native_search_directories_fn) (INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index e54580f22766..b22c5d620053 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -326,6 +326,11 @@ IN_PROCESS_APPLICATION::ExecuteApplication() 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())); + if (!m_shadowCopyDirectory.empty()) + { + RETURN_IF_NOT_ZERO(context->m_hostFxr.SetRuntimePropertyValue(APP_CONTEXT_BASE_DIRECTORY, Environment::GetCurrentDirectoryValue().c_str())); + } + bool clrThreadExited; { //Start CLR thread From 2143c8fdbe249b7fb88f8f7a46fc35093b824f32 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 17 Feb 2021 14:58:18 -0800 Subject: [PATCH 03/15] Fixing native tests --- .../InProcessRequestHandler/InProcessApplicationBase.cpp | 5 ++--- .../InProcessRequestHandler/InProcessApplicationBase.h | 3 +-- .../InProcessRequestHandler/ShuttingDownApplication.h | 2 +- .../StartupExceptionApplication.h | 2 +- .../InProcessRequestHandler/inprocessapplication.cpp | 9 +++++++-- .../OutOfProcessRequestHandler/outprocessapplication.cpp | 2 +- .../RequestHandlerLib/AppOfflineTrackingApplication.h | 3 +-- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp index 21f90f18c621..b876d6dc2656 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp @@ -5,9 +5,8 @@ InProcessApplicationBase::InProcessApplicationBase( IHttpServer& pHttpServer, - IHttpApplication& pHttpApplication, - const std::wstring& shadowCopyDirectory) - : AppOfflineTrackingApplication(pHttpApplication, shadowCopyDirectory), + IHttpApplication& pHttpApplication) + : AppOfflineTrackingApplication(pHttpApplication), m_fRecycleCalled(FALSE), m_pHttpServer(pHttpServer) { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h index 61656b487037..d8f141648c9a 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h @@ -11,8 +11,7 @@ class InProcessApplicationBase : public AppOfflineTrackingApplication InProcessApplicationBase( IHttpServer& pHttpServer, - IHttpApplication& pHttpApplication, - const std::wstring& shadowCoypDirectory); + IHttpApplication& pHttpApplication); ~InProcessApplicationBase() = default; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h index 5af0a43aac12..171ad4a2efff 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h @@ -32,7 +32,7 @@ class ShuttingDownApplication : public InProcessApplicationBase { public: ShuttingDownApplication(IHttpServer& pHttpServer, IHttpApplication& pHttpApplication) - : InProcessApplicationBase(pHttpServer, pHttpApplication, /* shadowCopyDirectory */ std::wstring()) + : InProcessApplicationBase(pHttpServer, pHttpApplication) { } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h index 3f1dc36ff1f9..d3e50e620fa8 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h @@ -25,7 +25,7 @@ class StartupExceptionApplication : public InProcessApplicationBase m_statusCode(statusCode), m_subStatusCode(subStatusCode), m_statusText(std::move(statusText)), - InProcessApplicationBase(pServer, pApplication, /* shadowCopyDirectory */ std::wstring()) + InProcessApplicationBase(pServer, pApplication) { } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index b22c5d620053..8d4be39a0984 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -23,8 +23,7 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( APPLICATION_PARAMETER* pParameters, DWORD nParameters) : InProcessApplicationBase(pHttpServer, - pApplication, - FindParameter("ShadowCopyDirectory", pParameters, nParameters)), + pApplication), m_Initialized(false), m_blockManagedCallbacks(true), m_waitForShutdown(true), @@ -39,6 +38,12 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( m_dotnetExeKnownLocation = knownLocation; } + const auto shadowCopyDirectory = FindParameter("ShadowCopyDirectory", pParameters, nParameters); + if (shadowCopyDirectory != nullptr) + { + m_shadowCopyDirectory = shadowCopyDirectory; + } + m_stringRedirectionOutput = std::make_shared(); } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp index c471262d3c20..897a97d17c05 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp @@ -8,7 +8,7 @@ OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION( IHttpApplication& pApplication, std::unique_ptr pConfig) : - AppOfflineTrackingApplication(pApplication, /* shadowCopyDirectory */ std::wstring()), + AppOfflineTrackingApplication(pApplication), m_fWebSocketSupported(WEBSOCKET_STATUS::WEBSOCKET_UNKNOWN), m_pConfig(std::move(pConfig)) { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h index 4cc655a35c41..2e2b145ece56 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h @@ -11,13 +11,12 @@ class AppOfflineTrackingApplication: public APPLICATION { public: - AppOfflineTrackingApplication(const IHttpApplication& application, const std::wstring& shadowCopyDirectory) + AppOfflineTrackingApplication(const IHttpApplication& application) : APPLICATION(application), m_applicationPath(application.GetApplicationPhysicalPath()), m_fileWatcher(nullptr), m_fAppOfflineProcessed(false) { - m_shadowCopyDirectory = shadowCopyDirectory; } ~AppOfflineTrackingApplication() override From 6bc26c39a19997b3a3e6559f76683b2d8497bf9e Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 17 Feb 2021 17:29:14 -0800 Subject: [PATCH 04/15] Nit --- .../IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index e944793da315..3758fb737441 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -109,7 +109,6 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext) if (FAILED_LOG(hr)) { - OBSERVE_CAUGHT_EXCEPTION(); EventLog::Error( ASPNETCORE_EVENT_ADD_APPLICATION_ERROR, ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG, @@ -147,6 +146,7 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext) } catch (...) { + OBSERVE_CAUGHT_EXCEPTION(); EventLog::Error( ASPNETCORE_CONFIGURATION_LOAD_ERROR, ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG, From 6ac2bf47e85197dbd4c4a57fc060a4a9cf521846 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 18 Feb 2021 09:04:33 -0800 Subject: [PATCH 05/15] nits --- .../IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 160140c60d74..1d3c88b1de86 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -96,8 +96,6 @@ FILE_WATCHER::Create( RETURN_IF_FAILED(_strFullName.Append(_strDirectoryName)); RETURN_IF_FAILED(_strFullName.Append(_strFileName)); - // TODO I'm concerned about how detecting a dll change will work if it too late - // // Resize change buffer to something "reasonable" // @@ -193,7 +191,6 @@ Win32 error pFileMonitor->m_fThreadExit = TRUE; - // TODO check instead for if a dll was changed here if (pFileMonitor->m_fShadowCopyEnabled) { // Cancel the timer to avoid it calling copy. @@ -334,7 +331,6 @@ FILE_WATCHER::TimerCallback( DWORD WINAPI FILE_WATCHER::CopyAndShutdown(LPVOID arg) { - auto watcher = (FILE_WATCHER*)arg; // Only copy and shutdown once From 88a794a46f346e255531c3a64a1e07d93033663b Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 18 Feb 2021 09:29:45 -0800 Subject: [PATCH 06/15] Wait for initialization or exit before calling shutdown --- .../inprocessapplication.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 8d4be39a0984..3816d3c2e00d 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -68,7 +68,18 @@ IN_PROCESS_APPLICATION::StopClr() // This has the state lock around it. LOG_INFO(L"Stopping CLR"); - if (!m_blockManagedCallbacks) + // Signal shutdown + if (m_pShutdownEvent != nullptr) + { + LOG_IF_FAILED(SetEvent(m_pShutdownEvent)); + } + + // Need to wait for either the app to be initialized, the worker thread to exit, or the shutdown timeout. + const HANDLE waitHandles[2] = { m_pInitializeEvent, m_workerThread.native_handle() }; + + const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, m_pConfig->QueryShutdownTimeLimitInMS()); + + if (!m_blockManagedCallbacks && waitResult != WAIT_OBJECT_0 + 1) { // 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, @@ -90,12 +101,6 @@ IN_PROCESS_APPLICATION::StopClr() } } - // 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 From 9ed16c05345f418c8c06278e2c8dd09641a75e57 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 18 Feb 2021 12:12:30 -0800 Subject: [PATCH 07/15] Catch exception from background thread --- .../AspNetCore/applicationinfo.cpp | 2 - .../inprocessapplication.cpp | 46 +++++++++++-------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index 3758fb737441..1145962988a3 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -245,10 +245,8 @@ APPLICATION_INFO::ShutDownApplication(const bool fServerInitiated) LOG_INFOF(L"Stopping application '%ls'", QueryApplicationInfoKey().c_str()); app->Stop(fServerInitiated); - LOG_INFO(L"Setting app to null"); SRWExclusiveLock lock(m_applicationLock); - LOG_INFO(L"lock acquired"); m_pApplication = nullptr; m_pApplicationFactory = nullptr; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 3816d3c2e00d..326d73607312 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -14,7 +14,7 @@ #include "Environment.h" #include "HostFxr.h" -IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; +IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( IHttpServer& pHttpServer, @@ -184,18 +184,26 @@ IN_PROCESS_APPLICATION::LoadManagedApplication(ErrorContext& errorContext) { if (p.path() != shadowCopyDir) { - std::filesystem::remove_all(p.path()); + try + { + std::filesystem::remove_all(p.path()); + } + catch (...) + { + OBSERVE_CAUGHT_EXCEPTION(); + } } } + }, m_shadowCopyDirectory); } 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"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() }; @@ -255,7 +263,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication() m_pConfig->QueryArguments(), errorContext, hostFxrResolutionResult - )); + )); hostFxrResolutionResult->GetArguments(context->m_argc, context->m_argv); THROW_IF_FAILED(SetEnvironmentVariablesOnWorkerProcess()); @@ -440,11 +448,11 @@ void IN_PROCESS_APPLICATION::QueueStop() 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)); + { + LOG_INFO(L"Starting in-process stop thread"); + application->Stop(false); + LOG_INFO(L"Stopping in-process stop thread"); + }, ::ReferenceApplication(this)); stoppingThread.detach(); } @@ -505,7 +513,7 @@ IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr& con context->m_exitCode = exitCode; context->m_hostFxr.Close(); } - __except(GetExceptionCode() != 0) + __except (GetExceptionCode() != 0) { LOG_INFOF(L"Managed threw an exception %d", GetExceptionCode()); @@ -519,7 +527,7 @@ IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr& con // in case of startup timeout // VOID -IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr &context) +IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr& context) { HandleWrapper moduleHandle; @@ -550,7 +558,7 @@ IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess() QueryApplicationPhysicalPath().c_str(), nullptr); - for (const auto & variable : variables) + 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()); @@ -584,7 +592,7 @@ IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) c QueryApplicationId().c_str(), QueryApplicationPhysicalPath().c_str(), context.m_exceptionCode - ); + ); } return; } @@ -618,8 +626,8 @@ IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) c HRESULT IN_PROCESS_APPLICATION::CreateHandler( - _In_ IHttpContext *pHttpContext, - _Out_ IREQUEST_HANDLER **pRequestHandler) + _In_ IHttpContext* pHttpContext, + _Out_ IREQUEST_HANDLER** pRequestHandler) { try { From 90ac490433d01efa47ff9cd2cdfa3c69bc32cfab Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 19 Feb 2021 09:08:50 -0800 Subject: [PATCH 08/15] wstring const --- .../IIS/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h index 6e7caa7912cb..9906251f532f 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h @@ -32,7 +32,7 @@ class ApplicationFactory HRESULT Execute( _In_ IHttpServer *pServer, _In_ IHttpContext *pHttpContext, - _In_ std::wstring shadowCopyDirectory, + _In_ std::wstring& shadowCopyDirectory, _Outptr_ IAPPLICATION **pApplication) const { // m_location.data() is const ptr copy to local to get mutable pointer From 67194e36f9117d794662a5431341a8ddb6e366ea Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 19 Feb 2021 10:32:01 -0800 Subject: [PATCH 09/15] Feedback --- .../AspNetCore/AspNetCore.vcxproj | 10 ++--- .../AspNetCore/HandlerResolver.cpp | 4 +- .../AspNetCore/HandlerResolver.h | 4 +- .../AspNetCore/ShimOptions.cpp | 3 +- .../AspNetCore/applicationinfo.cpp | 38 +++++++++---------- .../AspNetCore/applicationinfo.h | 1 - .../AspNetCore/applicationmanager.cpp | 2 +- .../CommonLib/Environment.cpp | 4 +- .../CommonLib/Environment.h | 4 +- .../AspNetCoreModuleV2/CommonLib/sttimer.h | 6 +-- .../inprocessapplication.cpp | 5 ++- .../inprocessapplication.h | 1 + .../AppOfflineTrackingApplication.cpp | 3 +- .../AppOfflineTrackingApplication.h | 4 +- .../RequestHandlerLib/filewatcher.cpp | 13 +++---- .../RequestHandlerLib/filewatcher.h | 10 ++--- src/Servers/IIS/copyToIIS.ps1 | 4 -- 17 files changed, 56 insertions(+), 60 deletions(-) delete mode 100644 src/Servers/IIS/copyToIIS.ps1 diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj index 187df260c1f8..da88b26f4c47 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj @@ -111,7 +111,7 @@ Windows true - kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib Source.def UseLinkTimeCodeGeneration @@ -147,7 +147,7 @@ Windows true - kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib Source.def UseLinkTimeCodeGeneration @@ -188,7 +188,7 @@ /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true Source.def - kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib UseLinkTimeCodeGeneration @@ -228,7 +228,7 @@ /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true Source.def - kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib UseLinkTimeCodeGeneration @@ -317,4 +317,4 @@ - \ No newline at end of file + diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp index 04985267d17f..03096d658699 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp @@ -29,7 +29,7 @@ HandlerResolver::HandlerResolver(HMODULE hModule, const IHttpServer &pServer) HRESULT HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication, - std::filesystem::path shadowCopyPath, + const std::filesystem::path& shadowCopyPath, const ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory, ErrorContext& errorContext) @@ -126,7 +126,7 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication } HRESULT -HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, std::filesystem::path shadowCopyPath, std::unique_ptr& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext) +HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, const std::filesystem::path& shadowCopyPath, std::unique_ptr& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext) { SRWExclusiveLock lock(m_requestHandlerLoadLock); if (m_loadedApplicationHostingModel != HOSTING_UNKNOWN) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h index ed36df3c4819..4ca4d231d950 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h @@ -15,12 +15,12 @@ class HandlerResolver { public: HandlerResolver(HMODULE hModule, const IHttpServer &pServer); - HRESULT GetApplicationFactory(const IHttpApplication &pApplication, std::filesystem::path shadowCopyPath, std::unique_ptr& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext); + HRESULT GetApplicationFactory(const IHttpApplication &pApplication, const std::filesystem::path& shadowCopyPath, std::unique_ptr& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext); void ResetHostingModel(); APP_HOSTING_MODEL GetHostingModel(); private: - HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, std::filesystem::path shadowCopyPath, const ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory, ErrorContext& errorContext); + HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const std::filesystem::path& shadowCopyPath, const ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory, ErrorContext& errorContext); HRESULT FindNativeAssemblyFromGlobalLocation(const ShimOptions& pConfiguration, PCWSTR libraryName, std::wstring& handlerDllPath); HRESULT FindNativeAssemblyFromHostfxr( const HostFxrResolutionResult& hostfxrOptions, diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp index efa16875373e..a7abe4ac5f66 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp @@ -47,7 +47,8 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : auto cleanShadowCopyDirectory = find_element(handlerSettings, CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT).value_or(std::wstring()); m_fCleanShadowCopyDirectory = equals_ignore_case(L"true", cleanShadowCopyDirectory); - m_strShadowCopyingDirectory = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY_DIRECTORY).value_or(std::wstring()); + m_strShadowCopyingDirectory = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY_DIRECTORY) + .value_or(m_fEnableShadowCopying ? L"ShadowCopyDirectory" : std::wstring()); m_strProcessPath = section->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH); m_strArguments = section->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index 1145962988a3..068c653a4bbb 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -20,7 +20,7 @@ #include "file_utility.h" extern HINSTANCE g_hServerModule; -extern BOOL g_fInAppOfflineShutdown; +extern BOOL g_fInAppOfflineShutdown; HRESULT APPLICATION_INFO::CreateHandler( @@ -83,24 +83,24 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext) return S_OK; } - if (g_fInAppOfflineShutdown) - { - m_pApplication = make_application( - pHttpApplication, - E_FAIL, - false /* disableStartupPage */, - "" /* responseContent */, - 503i16 /* statusCode */, - 0i16 /* subStatusCode */, - "Application Shutting Down"); - return S_OK; - } - try { const WebConfigConfigurationSource configurationSource(m_pServer.GetAdminManager(), pHttpApplication); ShimOptions options(configurationSource); + if (g_fInAppOfflineShutdown) + { + m_pApplication = make_application( + pHttpApplication, + E_FAIL, + false /* disableStartupPage */, + "" /* responseContent */, + 503i16 /* statusCode */, + 0i16 /* subStatusCode */, + "Application Shutting Down"); + return S_OK; + } + ErrorContext errorContext; errorContext.statusCode = 500i16; errorContext.subStatusCode = 0i16; @@ -198,10 +198,11 @@ APPLICATION_INFO::TryCreateApplication(IHttpContext& pHttpContext, const ShimOpt LOG_INFO(L"Creating handler application"); IAPPLICATION * newApplication; + std::wstring shadowCopyWstring = shadowCopyPath.wstring(); RETURN_IF_FAILED(m_pApplicationFactory->Execute( &m_pServer, &pHttpContext, - shadowCopyPath, + shadowCopyWstring, &newApplication)); m_pApplication.reset(newApplication); @@ -287,12 +288,11 @@ APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHt { try { - std::string::size_type size; - int intFileName = std::stoi(entry.path().filename().string(), &size); + int intFileName = std::stoi(entry.path().filename().string()); if (intFileName > directoryName) { directoryName = intFileName; - directoryNameStr = std::string(entry.path().string()); + directoryNameStr = entry.path().filename().string(); } } catch (...) @@ -303,7 +303,7 @@ APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHt } } - shadowCopyPath = shadowCopyPath / std::filesystem::path(directoryNameStr); + shadowCopyPath = shadowCopyPath / directoryNameStr; HRESULT hr = Environment::CopyToDirectory(physicalPath, shadowCopyPath, options.QueryCleanShadowCopyDirectory(), shadowCopyBaseDirectory.path().parent_path()); if (hr != S_OK) { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h index c75c78660feb..e5e33756df70 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h @@ -66,7 +66,6 @@ class APPLICATION_INFO: NonCopyable return false; } - private: HRESULT diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp index 6240892ccaa6..7bc3bd2599dd 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp @@ -109,7 +109,7 @@ APPLICATION_MANAGER::RecycleApplicationFromManager( if (itr->second->ConfigurationPathApplies(configurationPath)) { applicationsToRecycle.emplace_back(itr->second); - // Delete after shutting the application down to avoid creating + // Delay deleting an in-process app until after shutting the application down to avoid creating // another application info, which would just return app_offline. if (m_handlerResolver.GetHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp index b4feb060bda9..1391477a7a35 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp @@ -152,7 +152,7 @@ bool Environment::IsRunning64BitProcess() return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; } -HRESULT Environment::CopyToDirectory(std::wstring source, std::filesystem::path destination, bool cleanDest, std::filesystem::path directoryToIgnore) +HRESULT Environment::CopyToDirectory(const std::wstring& source, const std::filesystem::path& destination, bool cleanDest, const std::filesystem::path& directoryToIgnore) { if (cleanDest && std::filesystem::exists(destination)) { @@ -201,7 +201,7 @@ void Environment::CopyToDirectoryInner(const std::filesystem::path& source, cons } } -bool Environment::CheckUpToDate(std::wstring source, std::filesystem::path destination, const std::wstring& extension, const std::filesystem::path& directoryToIgnore) +bool Environment::CheckUpToDate(const std::wstring& source, const std::filesystem::path& destination, const std::wstring& extension, const std::filesystem::path& directoryToIgnore) { for (auto& path : std::filesystem::directory_iterator(source)) { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h index 8f6992535087..a0a4d4013c42 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h @@ -23,9 +23,9 @@ class Environment static bool IsRunning64BitProcess(); static - HRESULT CopyToDirectory(std::wstring source, std::filesystem::path destination, bool cleanDest, std::filesystem::path directoryToIgnore); + HRESULT CopyToDirectory(const std::wstring& source, const std::filesystem::path& destination, bool cleanDest, const std::filesystem::path& directoryToIgnore); static - bool CheckUpToDate(std::wstring source, std::filesystem::path destination, const std::wstring& extension, const std::filesystem::path& directoryToIgnore); + bool CheckUpToDate(const std::wstring& source, const std::filesystem::path& destination, const std::wstring& extension, const std::filesystem::path& directoryToIgnore); private: static void CopyToDirectoryInner(const std::filesystem::path& source_folder, const std::filesystem::path& target_folder, const std::filesystem::path& directoryToIgnore); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/sttimer.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/sttimer.h index 26d79d073781..86b62b6fcf2a 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/sttimer.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/sttimer.h @@ -118,13 +118,11 @@ class STTIMER VOID CALLBACK TimerCallback( - _In_ PTP_CALLBACK_INSTANCE Instance, + _In_ PTP_CALLBACK_INSTANCE , _In_ PVOID Context, - _In_ PTP_TIMER Timer + _In_ PTP_TIMER ) { - Instance; - Timer; STRU* pstruLogFilePath = (STRU*)Context; HANDLE hStdoutHandle = NULL; SECURITY_ATTRIBUTES saAttr = { 0 }; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 326d73607312..0441a6bb64f0 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -38,12 +38,14 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( m_dotnetExeKnownLocation = knownLocation; } - const auto shadowCopyDirectory = FindParameter("ShadowCopyDirectory", pParameters, nParameters); + const auto shadowCopyDirectory = FindParameter(s_shadowCopyDirectoryName, pParameters, nParameters); if (shadowCopyDirectory != nullptr) { m_shadowCopyDirectory = shadowCopyDirectory; } + m_shutdownTimeout = m_pConfig.get()->QueryShutdownTimeLimitInMS(); + m_stringRedirectionOutput = std::make_shared(); } @@ -109,7 +111,6 @@ IN_PROCESS_APPLICATION::StopClr() if (m_folderCleanupThread.joinable()) { - // Worker thread would wait for clr to finish and log error if required m_folderCleanupThread.join(); } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h index 10890168c182..2cefb9bbcd04 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h @@ -179,6 +179,7 @@ class IN_PROCESS_APPLICATION : public InProcessApplicationBase std::shared_ptr m_stringRedirectionOutput; inline static const LPCSTR s_exeLocationParameterName = "InProcessExeLocation"; + inline static const LPCSTR s_shadowCopyDirectoryName = "ShadowCopyDirectory"; VOID UnexpectedThreadExit(const ExecuteClrContext& context) const; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp index 7c02e4aeb8c1..e7dbdf196d47 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp @@ -45,7 +45,8 @@ HRESULT AppOfflineTrackingApplication::StartMonitoringAppOflineImpl() RETURN_IF_FAILED(m_fileWatcher->Create(m_applicationPath.c_str(), L"app_offline.htm", m_shadowCopyDirectory, - this)); + this, + m_shutdownTimeout)); return S_OK; } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h index 2e2b145ece56..8d4eafc0aad0 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h @@ -15,7 +15,8 @@ class AppOfflineTrackingApplication: public APPLICATION : APPLICATION(application), m_applicationPath(application.GetApplicationPhysicalPath()), m_fileWatcher(nullptr), - m_fAppOfflineProcessed(false) + m_fAppOfflineProcessed(false), + m_shutdownTimeout(120000) // default to 2 minutes { } @@ -40,6 +41,7 @@ class AppOfflineTrackingApplication: public APPLICATION // TODO protected bool m_detectedAppOffline; std::wstring m_shadowCopyDirectory; + DWORD m_shutdownTimeout; private: HRESULT StartMonitoringAppOflineImpl(); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 1d3c88b1de86..376ae6969ac2 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -65,8 +65,9 @@ HRESULT FILE_WATCHER::Create( _In_ PCWSTR pszDirectoryToMonitor, _In_ PCWSTR pszFileNameToMonitor, - _In_ std::wstring shadowCopyPath, - _In_ AppOfflineTrackingApplication* pApplication + _In_ const std::wstring& shadowCopyPath, + _In_ AppOfflineTrackingApplication* pApplication, + _In_ DWORD shutdownTimeout ) { m_shadowCopyPath = shadowCopyPath; @@ -309,7 +310,7 @@ HRESULT // Reset timer for dll checks LOG_INFO(L"Detected dll change, resetting timer callback which will eventually trigger shutdown."); m_Timer.CancelTimer(); - m_Timer.InitializeTimer(FILE_WATCHER::TimerCallback, this, 5000, 3000); + m_Timer.InitializeTimer(FILE_WATCHER::TimerCallback, this, 5000, INFINITE); } return S_OK; @@ -329,10 +330,8 @@ FILE_WATCHER::TimerCallback( CopyAndShutdown((FILE_WATCHER*)Context); } -DWORD WINAPI FILE_WATCHER::CopyAndShutdown(LPVOID arg) +DWORD WINAPI FILE_WATCHER::CopyAndShutdown(FILE_WATCHER* watcher) { - auto watcher = (FILE_WATCHER*)arg; - // Only copy and shutdown once SRWExclusiveLock lock(watcher->m_copyLock); if (watcher->m_copied) @@ -443,7 +442,7 @@ FILE_WATCHER::StopMonitor() PostQueuedCompletionStatus(m_hCompletionPort, 0, FILE_WATCHER_SHUTDOWN_KEY, NULL); WaitForMonitor(200); - if (m_fShadowCopyEnabled && m_pDoneCopyEvent != nullptr) + if (m_fShadowCopyEnabled) { // If we are shadow copying, wait for the copying to finish. WaitForSingleObject(m_pDoneCopyEvent, 30000); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h index e5a963d05e55..a804c6c7d91e 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -28,12 +28,10 @@ class FILE_WATCHER{ HRESULT Create( _In_ PCWSTR pszDirectoryToMonitor, - _In_ PCWSTR pszFileNameToMonitor, - - _In_ std::wstring shadowCopyPath, - - _In_ AppOfflineTrackingApplication *pApplication + _In_ const std::wstring& shadowCopyPath, + _In_ AppOfflineTrackingApplication *pApplication, + _In_ DWORD shutdownTimeout ); static @@ -50,7 +48,7 @@ class FILE_WATCHER{ _In_ PVOID Context, _In_ PTP_TIMER Timer); - static DWORD WINAPI CopyAndShutdown(LPVOID); + static DWORD WINAPI CopyAndShutdown(FILE_WATCHER* watcher); HRESULT HandleChangeCompletion(DWORD cbCompletion); diff --git a/src/Servers/IIS/copyToIIS.ps1 b/src/Servers/IIS/copyToIIS.ps1 deleted file mode 100644 index 696932e5ded4..000000000000 --- a/src/Servers/IIS/copyToIIS.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -net stop was /Y -.\build.cmd -cp "C:\Users\jukotali\code\aspnetcore\artifacts\bin\AspNetCoreModuleShim\x64\Debug\*" "C:\Program Files\IIS\Asp.Net Core Module\V2\" -Force -net start w3svc /Y \ No newline at end of file From 75aa559428e0ea00964a683b806a2daa39b454e3 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 19 Feb 2021 10:45:10 -0800 Subject: [PATCH 10/15] nit --- .../IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp | 1 + .../IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 376ae6969ac2..a643f3dde94c 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -72,6 +72,7 @@ FILE_WATCHER::Create( { m_shadowCopyPath = shadowCopyPath; m_fShadowCopyEnabled = !shadowCopyPath.empty(); + m_shutdownTimeout = shutdownTimeout; RETURN_LAST_ERROR_IF_NULL(m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h index a804c6c7d91e..470edbb5eb21 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -72,6 +72,7 @@ class FILE_WATCHER{ LONG _lStopMonitorCalled {}; bool m_fShadowCopyEnabled; std::wstring m_shadowCopyPath; + DWORD m_shutdownTimeout; OVERLAPPED _overlapped; std::unique_ptr _pApplication; }; From 9c418b77265f1553c5df425649b4dd5733e2a856 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 19 Feb 2021 10:46:09 -0800 Subject: [PATCH 11/15] nit --- .../IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index a643f3dde94c..461737f802f6 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -446,7 +446,7 @@ FILE_WATCHER::StopMonitor() if (m_fShadowCopyEnabled) { // If we are shadow copying, wait for the copying to finish. - WaitForSingleObject(m_pDoneCopyEvent, 30000); + WaitForSingleObject(m_pDoneCopyEvent, m_shutdownTimeout); } // Release application reference From 14485ea2a6919917c7ad8815fff3fbd47d920b75 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 15 Mar 2021 10:37:05 -0700 Subject: [PATCH 12/15] Disable for IISExpress --- .../IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp | 5 +++-- .../ShadowCopyTests.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) rename src/Servers/IIS/IIS/test/{Common.FunctionalTests => IIS.FunctionalTests}/ShadowCopyTests.cs (99%) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index 068c653a4bbb..1bf3054d7f4f 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -243,7 +243,7 @@ APPLICATION_INFO::ShutDownApplication(const bool fServerInitiated) } app = m_pApplication.get(); } - + LOG_INFOF(L"Stopping application '%ls'", QueryApplicationInfoKey().c_str()); app->Stop(fServerInitiated); @@ -258,7 +258,8 @@ APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHt { std::filesystem::path shadowCopyPath; - if (options.QueryShadowCopyEnabled()) + // Only support shadow copying for IIS. + if (options.QueryShadowCopyEnabled() && !m_pServer.IsCommandLineLaunch()) { shadowCopyPath = options.QueryShadowCopyDirectory(); std::wstring physicalPath = pHttpContext.GetApplication()->GetApplicationPhysicalPath(); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ShadowCopyTests.cs b/src/Servers/IIS/IIS/test/IIS.FunctionalTests/ShadowCopyTests.cs similarity index 99% rename from src/Servers/IIS/IIS/test/Common.FunctionalTests/ShadowCopyTests.cs rename to src/Servers/IIS/IIS/test/IIS.FunctionalTests/ShadowCopyTests.cs index a02e96184617..86616d17b577 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ShadowCopyTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.FunctionalTests/ShadowCopyTests.cs @@ -236,7 +236,7 @@ private static void DirectoryCopy(string sourceDirName, string destDirName, bool DirectoryInfo[] dirs = dir.GetDirectories(); - // If the destination directory doesn't exist, create it. + // If the destination directory doesn't exist, create it. Directory.CreateDirectory(destDirName); // Get the files in the directory and copy them to the new location. From 4715c93a00412daee2a6792b3094b5b3c171c003 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 15 Mar 2021 15:30:02 -0700 Subject: [PATCH 13/15] Feedback --- .../AspNetCore/applicationinfo.cpp | 13 +++--- .../AspNetCore/applicationinfo.h | 2 +- .../inprocessapplication.cpp | 1 + .../Properties/launchSettings.json | 40 +++++++++++++++++++ 4 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 src/Servers/IIS/IIS/perf/Microbenchmarks/Properties/launchSettings.json diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index 1bf3054d7f4f..78db3c00ea3d 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -3,8 +3,6 @@ #include "applicationinfo.h" -#include "rpcdce.h" -#include "Rpc.h" #include "proxymodule.h" #include "HostFxrResolver.h" #include "debugutil.h" @@ -93,7 +91,7 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext) m_pApplication = make_application( pHttpApplication, E_FAIL, - false /* disableStartupPage */, + options.QueryDisableStartupPage() /* disableStartupPage */, "" /* responseContent */, 503i16 /* statusCode */, 0i16 /* subStatusCode */, @@ -192,7 +190,7 @@ APPLICATION_INFO::TryCreateApplication(IHttpContext& pHttpContext, const ShimOpt } } - std::filesystem::path shadowCopyPath = HandleShadowCopy(options, pHttpContext); + auto shadowCopyPath = HandleShadowCopy(options, pHttpContext); RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), shadowCopyPath, m_pApplicationFactory, options, error)); LOG_INFO(L"Creating handler application"); @@ -253,7 +251,7 @@ APPLICATION_INFO::ShutDownApplication(const bool fServerInitiated) m_pApplicationFactory = nullptr; } -std::wstring +std::filesystem::path APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext) { std::filesystem::path shadowCopyPath; @@ -289,11 +287,12 @@ APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHt { try { - int intFileName = std::stoi(entry.path().filename().string()); + auto tempDirName = entry.path().filename().string(); + int intFileName = std::stoi(tempDirName); if (intFileName > directoryName) { directoryName = intFileName; - directoryNameStr = entry.path().filename().string(); + directoryNameStr = tempDirName; } } catch (...) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h index e5e33756df70..a54107eb8bee 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h @@ -79,7 +79,7 @@ class APPLICATION_INFO: NonCopyable HRESULT TryCreateApplication(IHttpContext& pHttpContext, const ShimOptions& options, ErrorContext& error); - std::wstring + std::filesystem::path HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext); IHttpServer &m_pServer; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 0441a6bb64f0..c94236af42e6 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -81,6 +81,7 @@ IN_PROCESS_APPLICATION::StopClr() const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, m_pConfig->QueryShutdownTimeLimitInMS()); + // If waitResults != WAIT_OBJECT_0 + 1, it means main hasn't returned, so okay to call into it. if (!m_blockManagedCallbacks && waitResult != WAIT_OBJECT_0 + 1) { // We cannot call into managed if the dll is detaching from the process. diff --git a/src/Servers/IIS/IIS/perf/Microbenchmarks/Properties/launchSettings.json b/src/Servers/IIS/IIS/perf/Microbenchmarks/Properties/launchSettings.json new file mode 100644 index 000000000000..fe76b29b53d8 --- /dev/null +++ b/src/Servers/IIS/IIS/perf/Microbenchmarks/Properties/launchSettings.json @@ -0,0 +1,40 @@ +{ + "iisSettings": { + "windowsAuthentication": true, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5762/", + "sslPort": 0 + } + }, + "profiles": { + "ANCM IIS Express": { + "commandName": "Executable", + "executablePath": "$(IISExpressPath)", + "commandLineArgs": "$(IISExpressArguments)", + "environmentVariables": { + "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", + "LAUNCHER_ARGS": "$(TargetPath)", + "ASPNETCORE_ENVIRONMENT": "Development", + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" + } + }, + "ANCM IIS": { + "commandName": "Executable", + "executablePath": "$(IISPath)", + "commandLineArgs": "$(IISArguments)", + "environmentVariables": { + "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", + "LAUNCHER_ARGS": "$(TargetPath)", + "ASPNETCORE_ENVIRONMENT": "Development", + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" + } + } + } +} From 254ab40d85b6948b8e677136fcc85079111b3574 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 18 Mar 2021 14:49:37 -0700 Subject: [PATCH 14/15] Mark experimental --- .../IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp | 8 ++++---- .../IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h | 4 ++-- .../IIS/test/IIS.FunctionalTests/ShadowCopyTests.cs | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp index a7abe4ac5f66..c43b2c62a0c2 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp @@ -8,7 +8,7 @@ #include "Environment.h" #define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion" -#define CS_ASPNETCORE_SHADOW_COPY L"enableShadowCopy" +#define CS_ASPNETCORE_SHADOW_COPY L"experimentalEnableShadowCopy" #define CS_ASPNETCORE_SHADOW_COPY_DIRECTORY L"shadowCopyDirectory" #define CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT L"cleanShadowCopyDirectory" @@ -41,14 +41,14 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : m_strHandlerVersion = find_element(handlerSettings, CS_ASPNETCORE_HANDLER_VERSION).value_or(std::wstring()); } - auto enableShadowCopyElement = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY).value_or(std::wstring()); - m_fEnableShadowCopying = equals_ignore_case(L"true", enableShadowCopyElement); + auto experimentalEnableShadowCopyElement = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY).value_or(std::wstring()); + m_fexperimentalEnableShadowCopying = equals_ignore_case(L"true", experimentalEnableShadowCopyElement); auto cleanShadowCopyDirectory = find_element(handlerSettings, CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT).value_or(std::wstring()); m_fCleanShadowCopyDirectory = equals_ignore_case(L"true", cleanShadowCopyDirectory); m_strShadowCopyingDirectory = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY_DIRECTORY) - .value_or(m_fEnableShadowCopying ? L"ShadowCopyDirectory" : std::wstring()); + .value_or(m_fexperimentalEnableShadowCopying ? L"ShadowCopyDirectory" : std::wstring()); m_strProcessPath = section->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH); m_strArguments = section->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h index 76a7113e5e91..9674e9a0244f 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h @@ -68,7 +68,7 @@ class ShimOptions: NonCopyable bool QueryShadowCopyEnabled() const noexcept { - return m_fEnableShadowCopying; + return m_fexperimentalEnableShadowCopying; } bool @@ -94,7 +94,7 @@ class ShimOptions: NonCopyable bool m_fStdoutLogEnabled; bool m_fDisableStartupPage; bool m_fShowDetailedErrors; - bool m_fEnableShadowCopying; + bool m_fexperimentalEnableShadowCopying; bool m_fCleanShadowCopyDirectory; std::wstring m_strShadowCopyingDirectory; }; diff --git a/src/Servers/IIS/IIS/test/IIS.FunctionalTests/ShadowCopyTests.cs b/src/Servers/IIS/IIS/test/IIS.FunctionalTests/ShadowCopyTests.cs index 86616d17b577..fed5da275e4a 100644 --- a/src/Servers/IIS/IIS/test/IIS.FunctionalTests/ShadowCopyTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.FunctionalTests/ShadowCopyTests.cs @@ -23,7 +23,7 @@ public async Task ShadowCopyDoesNotLockFiles() { using var directory = TempDirectory.Create(); var deploymentParameters = Fixture.GetBaseDeploymentParameters(); - deploymentParameters.HandlerSettings["enableShadowCopy"] = "true"; + deploymentParameters.HandlerSettings["experimentalEnableShadowCopy"] = "true"; deploymentParameters.HandlerSettings["shadowCopyDirectory"] = directory.DirectoryPath; var deploymentResult = await DeployAsync(deploymentParameters); @@ -50,7 +50,7 @@ public async Task ShadowCopyRelativeInSameDirectoryWorks() { var directoryName = Path.GetRandomFileName(); var deploymentParameters = Fixture.GetBaseDeploymentParameters(); - deploymentParameters.HandlerSettings["enableShadowCopy"] = "true"; + deploymentParameters.HandlerSettings["experimentalEnableShadowCopy"] = "true"; deploymentParameters.HandlerSettings["shadowCopyDirectory"] = directoryName; var deploymentResult = await DeployAsync(deploymentParameters); @@ -81,7 +81,7 @@ public async Task ShadowCopyRelativeOutsideDirectoryWorks() { using var directory = TempDirectory.Create(); var deploymentParameters = Fixture.GetBaseDeploymentParameters(); - deploymentParameters.HandlerSettings["enableShadowCopy"] = "true"; + deploymentParameters.HandlerSettings["experimentalEnableShadowCopy"] = "true"; deploymentParameters.HandlerSettings["shadowCopyDirectory"] = $"..\\{directory.DirectoryInfo.Name}"; deploymentParameters.ApplicationPath = directory.DirectoryPath; @@ -115,7 +115,7 @@ public async Task ShadowCopySingleFileChangedWorks() { using var directory = TempDirectory.Create(); var deploymentParameters = Fixture.GetBaseDeploymentParameters(); - deploymentParameters.HandlerSettings["enableShadowCopy"] = "true"; + deploymentParameters.HandlerSettings["experimentalEnableShadowCopy"] = "true"; deploymentParameters.HandlerSettings["shadowCopyDirectory"] = directory.DirectoryPath; var deploymentResult = await DeployAsync(deploymentParameters); @@ -151,7 +151,7 @@ public async Task ShadowCopyE2EWorksWithFolderPresent() { using var directory = TempDirectory.Create(); var deploymentParameters = Fixture.GetBaseDeploymentParameters(); - deploymentParameters.HandlerSettings["enableShadowCopy"] = "true"; + deploymentParameters.HandlerSettings["experimentalEnableShadowCopy"] = "true"; deploymentParameters.HandlerSettings["shadowCopyDirectory"] = directory.DirectoryPath; var deploymentResult = await DeployAsync(deploymentParameters); From dca5ec4a9bdd71763ede9f12e31a81acf341ba9c Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 19 Mar 2021 11:14:05 -0700 Subject: [PATCH 15/15] Don't recursive copy --- .../IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp | 2 +- .../IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp | 6 ++++-- .../AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index 78db3c00ea3d..043e59f08743 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -304,7 +304,7 @@ APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHt } shadowCopyPath = shadowCopyPath / directoryNameStr; - HRESULT hr = Environment::CopyToDirectory(physicalPath, shadowCopyPath, options.QueryCleanShadowCopyDirectory(), shadowCopyBaseDirectory.path().parent_path()); + HRESULT hr = Environment::CopyToDirectory(physicalPath, shadowCopyPath, options.QueryCleanShadowCopyDirectory(), std::filesystem::canonical(shadowCopyBaseDirectory.path())); if (hr != S_OK) { return std::wstring(); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp index 1391477a7a35..95fa1bce3c61 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp @@ -192,8 +192,10 @@ void Environment::CopyToDirectoryInner(const std::filesystem::path& source, cons } else if (path.is_directory()) { - auto sourceInnerDirectory = std::filesystem::directory_entry(path); - if (sourceInnerDirectory.path() != directoryToIgnore) + auto sourceInnerDirectory = path.path(); + + // Make sure we aren't navigating into shadow copy directory. + if (sourceInnerDirectory.wstring().rfind(directoryToIgnore, 0) != 0) { CopyToDirectoryInner(path.path(), destination / path.path().filename(), directoryToIgnore); } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 461737f802f6..f4baec9f40d2 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -366,7 +366,7 @@ DWORD WINAPI FILE_WATCHER::CopyAndShutdown(FILE_WATCHER* watcher) // Copy contents before shutdown try { - Environment::CopyToDirectory(watcher->_strDirectoryName.QueryStr(), destination, false, parentDirectory); + Environment::CopyToDirectory(watcher->_strDirectoryName.QueryStr(), destination, false, std::filesystem::canonical(parentDirectory)); } catch (...) {