From c894584777494af0264305b47078c077af732d0c Mon Sep 17 00:00:00 2001
From: Javier Calvarro Nelson <jacalvar@microsoft.com>
Date: Tue, 17 Aug 2021 21:59:55 +0200
Subject: [PATCH] [Spa] Try additional hooks to kill the application (#34427)

* [Spa] Brings back the old application stopping hook for redundancy.
* Uses powershell script to monitor the .NET process on windows from outside and kills all the SPA processes when it detects the .net process has been killed.
---
 .../Spa/SpaProxy/src/SpaProxyLaunchManager.cs | 56 ++++++++++++++++---
 1 file changed, 47 insertions(+), 9 deletions(-)

diff --git a/src/Middleware/Spa/SpaProxy/src/SpaProxyLaunchManager.cs b/src/Middleware/Spa/SpaProxy/src/SpaProxyLaunchManager.cs
index 336f4e1b229..25be597f667 100644
--- a/src/Middleware/Spa/SpaProxy/src/SpaProxyLaunchManager.cs
+++ b/src/Middleware/Spa/SpaProxy/src/SpaProxyLaunchManager.cs
@@ -1,15 +1,12 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using System;
 using System.Diagnostics;
-using System.IO;
 using System.Net.Http;
 using System.Net.Http.Headers;
-using System.Threading;
-using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 
 namespace Microsoft.AspNetCore.SpaProxy
 {
@@ -25,16 +22,16 @@ namespace Microsoft.AspNetCore.SpaProxy
 
         public SpaProxyLaunchManager(
             ILogger<SpaProxyLaunchManager> logger,
+            IHostApplicationLifetime appLifetime,
             IOptions<SpaDevelopmentServerOptions> options)
         {
             _options = options.Value;
             _logger = logger;
+            appLifetime.ApplicationStopping.Register(() => Dispose(true));
         }
 
         public void StartInBackground(CancellationToken cancellationToken)
         {
-            _logger.LogInformation($"No SPA development server running at {_options.ServerUrl} found.");
-
             // We are not waiting for the SPA proxy to launch, instead we are going to rely on a piece of
             // middleware to display an HTML document while the SPA proxy is not ready, refresh every three
             // seconds and redirect to the SPA proxy url once it is ready.
@@ -45,6 +42,7 @@ namespace Microsoft.AspNetCore.SpaProxy
             {
                 if (_launchTask == null)
                 {
+                    _logger.LogInformation($"No SPA development server running at {_options.ServerUrl} found.");
                     _launchTask = UpdateStatus(StartSpaProcessAndProbeForLiveness(cancellationToken));
                 }
             }
@@ -190,6 +188,46 @@ namespace Microsoft.AspNetCore.SpaProxy
                     WorkingDirectory = Path.Combine(AppContext.BaseDirectory, _options.WorkingDirectory)
                 };
                 _spaProcess = Process.Start(info);
+                if (OperatingSystem.IsWindows() && _spaProcess != null)
+                {
+                    var stopScript = $@"do{{
+  try
+  {{
+    $processId = Get-Process -PID {Environment.ProcessId} -ErrorAction Stop;
+  }}catch
+  {{
+    $processId = $null;
+  }}
+  Start-Sleep -Seconds 1;
+}}while($processId -ne $null);
+
+try
+{{
+  taskkill /T /F /PID {_spaProcess.Id};
+}}
+catch
+{{
+}}";
+                    var stopScriptInfo = new ProcessStartInfo(
+                        "powershell.exe",
+                        string.Join(" ", "-NoProfile", "-C", stopScript))
+                    {
+                        CreateNoWindow = true,
+                        WorkingDirectory = Path.Combine(AppContext.BaseDirectory, _options.WorkingDirectory)
+                    };
+
+                    var stopProcess = Process.Start(stopScriptInfo);
+                    if (stopProcess == null || stopProcess.HasExited)
+                    {
+                        _logger.LogWarning($"SPA process shutdown script '{stopProcess?.Id}' failed to start. The SPA proxy might" +
+                            $" remain open if the dotnet process is terminated abruptly. Use the operating system command to kill" +
+                            $"the process tree for {_spaProcess.Id}");
+                    }
+                    else
+                    {
+                        _logger.LogDebug($"Watch process '{stopProcess}' started.");
+                    }
+                }
             }
             catch (Exception exception)
             {
@@ -199,7 +237,7 @@ namespace Microsoft.AspNetCore.SpaProxy
 
         public Task StopAsync(CancellationToken cancellationToken)
         {
-            // We don't need to do anything here since Dispose will take care of cleaning up the process if necessary.
+            Dispose(true);
             return Task.CompletedTask;
         }
 
-- 
GitLab