Просмотр исходного кода

[3.1.x] Fix dotnet.exe process recovery after abnormal exit in… (#17103)

Justin Kotalik 6 лет назад
Родитель
Сommit
9fded4c4aa

+ 8 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/config_utility.h

@@ -14,6 +14,7 @@ class ConfigUtility
     #define CS_ASPNETCORE_HANDLER_SETTINGS                   L"handlerSettings"
     #define CS_ASPNETCORE_HANDLER_VERSION                    L"handlerVersion"
     #define CS_ASPNETCORE_DEBUG_FILE                         L"debugFile"
+    #define CS_ASPNETCORE_ENABLE_OUT_OF_PROCESS_CONSOLE_REDIRECTION L"enableOutOfProcessConsoleRedirection"
     #define CS_ASPNETCORE_DEBUG_LEVEL                        L"debugLevel"
     #define CS_ASPNETCORE_HANDLER_SETTINGS_NAME              L"name"
     #define CS_ASPNETCORE_HANDLER_SETTINGS_VALUE             L"value"
@@ -40,6 +41,13 @@ public:
         return FindKeyValuePair(pElement, CS_ASPNETCORE_DEBUG_LEVEL, strDebugFile);
     }
 
+    static
+    HRESULT
+    FindEnableOutOfProcessConsoleRedirection(IAppHostElement* pElement, STRU& strEnableOutOfProcessConsoleRedirection)
+    {
+        return FindKeyValuePair(pElement, CS_ASPNETCORE_ENABLE_OUT_OF_PROCESS_CONSOLE_REDIRECTION, strEnableOutOfProcessConsoleRedirection);
+    }
+
 private:
     static
     HRESULT

+ 1 - 0
src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.cpp

@@ -158,6 +158,7 @@ PROCESS_MANAGER::GetProcess(
                     pConfig->QueryAnonymousAuthEnabled(),
                     pConfig->QueryEnvironmentVariables(),
                     pConfig->QueryStdoutLogEnabled(),
+                    pConfig->QueryEnableOutOfProcessConsoleRedirection(),
                     fWebsocketSupported,
                     pConfig->QueryStdoutLogFile(),
                     pConfig->QueryApplicationPhysicalPath(),   // physical path

+ 28 - 4
src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp

@@ -25,6 +25,7 @@ SERVER_PROCESS::Initialize(
     BOOL                  fAnonymousAuthEnabled,
     std::map<std::wstring, std::wstring, ignore_case_comparer>& pEnvironmentVariables,
     BOOL                  fStdoutLogEnabled,
+    BOOL                  fEnableOutOfProcessConsoleRedirection,
     BOOL                  fWebSocketSupported,
     STRU                  *pstruStdoutLogFile,
     STRU                  *pszAppPhysicalPath,
@@ -43,6 +44,7 @@ SERVER_PROCESS::Initialize(
     m_fWindowsAuthEnabled = fWindowsAuthEnabled;
     m_fBasicAuthEnabled = fBasicAuthEnabled;
     m_fAnonymousAuthEnabled = fAnonymousAuthEnabled;
+    m_fEnableOutOfProcessConsoleRedirection = fEnableOutOfProcessConsoleRedirection;
     m_pProcessManager->ReferenceProcessManager();
     m_fDebuggerAttached = FALSE;
 
@@ -1030,6 +1032,15 @@ SERVER_PROCESS::SetupStdHandles(
     saAttr.bInheritHandle = TRUE;
     saAttr.lpSecurityDescriptor = NULL;
 
+    if (!m_fEnableOutOfProcessConsoleRedirection)
+    {
+        pStartupInfo->dwFlags = STARTF_USESTDHANDLES;
+        pStartupInfo->hStdInput = INVALID_HANDLE_VALUE;
+        pStartupInfo->hStdError = INVALID_HANDLE_VALUE;
+        pStartupInfo->hStdOutput = INVALID_HANDLE_VALUE;
+        return hr;
+    }
+
     if (!m_fStdoutLogEnabled)
     {
         CreatePipe(&m_hStdoutHandle, &m_hStdErrWritePipe, &saAttr, 0 /*nSize*/);
@@ -1770,6 +1781,8 @@ SERVER_PROCESS::SERVER_PROCESS() :
     m_dwListeningProcessId(0),
     m_hListeningProcessHandle(NULL),
     m_hShutdownHandle(NULL),
+    m_hStdErrWritePipe(NULL),
+    m_hReadThread(nullptr),
     m_randomGenerator(std::random_device()())
 {
     //InterlockedIncrement(&g_dwActiveServerProcesses);
@@ -1866,13 +1879,15 @@ SERVER_PROCESS::~SERVER_PROCESS()
         m_pProcessManager = NULL;
     }
 
-    if (m_hStdoutHandle != NULL)
+    if (m_hStdErrWritePipe != NULL)
     {
-        if (m_hStdoutHandle != INVALID_HANDLE_VALUE)
+        if (m_hStdErrWritePipe != INVALID_HANDLE_VALUE)
         {
-            CloseHandle(m_hStdoutHandle);
+            FlushFileBuffers(m_hStdErrWritePipe);
+            CloseHandle(m_hStdErrWritePipe);
         }
-        m_hStdoutHandle = NULL;
+
+        m_hStdErrWritePipe = NULL;
     }
 
     // Forces ReadFile to cancel, causing the read loop to complete.
@@ -1907,6 +1922,15 @@ SERVER_PROCESS::~SERVER_PROCESS()
         m_hReadThread = nullptr;
     }
 
+    if (m_hStdoutHandle != NULL)
+    {
+        if (m_hStdoutHandle != INVALID_HANDLE_VALUE)
+        {
+            CloseHandle(m_hStdoutHandle);
+        }
+        m_hStdoutHandle = NULL;
+    }
+
     if (m_fStdoutLogEnabled)
     {
         m_Timer.CancelTimer();

+ 2 - 0
src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.h

@@ -37,6 +37,7 @@ public:
         _In_ BOOL                  fAnonymousAuthEnabled,
         _In_ std::map<std::wstring, std::wstring, ignore_case_comparer>& pEnvironmentVariables,
         _In_ BOOL                  fStdoutLogEnabled,
+        _In_ BOOL                  fDisableRedirection,
         _In_ BOOL                  fWebSocketSupported,
         _In_ STRU                 *pstruStdoutLogFile,
         _In_ STRU                 *pszAppPhysicalPath,
@@ -253,6 +254,7 @@ private:
     BOOL                    m_fBasicAuthEnabled;
     BOOL                    m_fAnonymousAuthEnabled;
     BOOL                    m_fDebuggerAttached;
+    BOOL                    m_fEnableOutOfProcessConsoleRedirection;
 
     STTIMER                 m_Timer;
     SOCKET                  m_socket;

+ 6 - 0
src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp

@@ -379,6 +379,12 @@ REQUESTHANDLER_CONFIG::Populate(
         goto Finished;
     }
 
+    hr = ConfigUtility::FindEnableOutOfProcessConsoleRedirection(pAspNetCoreElement, m_fEnableOutOfProcessConsoleRedirection);
+    if (FAILED(hr))
+    {
+        goto Finished;
+    }
+
 Finished:
 
     if (pAspNetCoreElement != NULL)

+ 7 - 0
src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h

@@ -218,6 +218,12 @@ public:
         return &m_struConfigPath;
     }
 
+    BOOL
+    QueryEnableOutOfProcessConsoleRedirection()
+    {
+        return !m_fEnableOutOfProcessConsoleRedirection.Equals(L"false", 1);
+    }
+
 protected:
 
     //
@@ -255,6 +261,7 @@ protected:
     BOOL                   m_fWindowsAuthEnabled;
     BOOL                   m_fBasicAuthEnabled;
     BOOL                   m_fAnonymousAuthEnabled;
+    STRU                   m_fEnableOutOfProcessConsoleRedirection;
     APP_HOSTING_MODEL      m_hostingModel;
     std::map<std::wstring, std::wstring, ignore_case_comparer> m_pEnvironmentVariables;
     STRU                   m_struHostFxrLocation;

+ 16 - 0
src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs

@@ -233,6 +233,22 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
             EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.OutOfProcessFailedToStart(deploymentResult, "Wow!"), Logger);
         }
 
+        [ConditionalFact]
+        [RequiresNewShim]
+        public async Task DisableRedirectionNoLogs()
+        {
+            var deploymentParameters = Fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess);
+            deploymentParameters.HandlerSettings["enableOutOfProcessConsoleRedirection"] = "false";
+            deploymentParameters.TransformArguments((a, _) => $"{a} ConsoleWriteSingle");
+            var deploymentResult = await DeployAsync(deploymentParameters);
+
+            var response = await deploymentResult.HttpClient.GetAsync("Test");
+
+            StopServer();
+
+            EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.OutOfProcessFailedToStart(deploymentResult, ""), Logger);
+        }
+
         [ConditionalFact]
         public async Task CaptureLogsForOutOfProcessWhenProcessFailsToStart30KbMax()
         {

+ 49 - 0
src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs

@@ -84,6 +84,55 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.OutOfProcess
             Assert.Equal(HttpStatusCode.BadGateway, response.StatusCode);
         }
 
+        [ConditionalTheory]
+        [MemberData(nameof(TestVariants))]
+        [RequiresNewShim]
+        public async Task ShutdownMultipleTimesWorks(TestVariant variant)
+        {
+            // Must publish to set env vars in web.config
+            var deploymentParameters = Fixture.GetBaseDeploymentParameters(variant);    
+
+            var deploymentResult = await DeployAsync(deploymentParameters);
+            
+            // Shutdown once
+            var response = await deploymentResult.HttpClient.GetAsync("/Shutdown");
+            
+            // Wait for server to start again.
+            int i;
+            for (i = 0; i < 10; i++)
+            {
+                // ANCM should eventually recover from being shutdown multiple times.
+                response = await deploymentResult.HttpClient.GetAsync("/HelloWorld");
+                if (response.IsSuccessStatusCode)
+                {
+                    break;
+                }
+            }
+            
+            if (i == 10)
+            {
+                // Didn't restart after 10 retries
+                Assert.False(true);
+            }
+            
+            // Shutdown again
+            response = await deploymentResult.HttpClient.GetAsync("/Shutdown");
+            
+            // return if server starts again.
+            for (i = 0; i < 10; i++)
+            {
+                // ANCM should eventually recover from being shutdown multiple times.
+                response = await deploymentResult.HttpClient.GetAsync("/HelloWorld");
+                if (response.IsSuccessStatusCode)
+                {
+                    return;
+                }
+            }
+            
+            // Test failure if this happens.
+            Assert.False(true);
+        }
+
         private static int GetUnusedRandomPort()
         {
             // Large number of retries to prevent test failures due to port collisions, but not infinite