Skip to content

Commit 22b077e

Browse files
authored
[SPA] Ensure spawned NPM processes are terminated when the dotnet process is ungracefully terminated (#35591)
Adds a script to terminate the spawned NPM processes automatically when the host dotnet process is ungracefully terminated.
1 parent 8dd8359 commit 22b077e

File tree

1 file changed

+88
-24
lines changed

1 file changed

+88
-24
lines changed

src/Middleware/Spa/SpaProxy/src/SpaProxyLaunchManager.cs

Lines changed: 88 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,27 @@ private void LaunchDevelopmentProxy()
188188
WorkingDirectory = Path.Combine(AppContext.BaseDirectory, _options.WorkingDirectory)
189189
};
190190
_spaProcess = Process.Start(info);
191-
if (OperatingSystem.IsWindows() && _spaProcess != null)
191+
if (_spaProcess != null && !_spaProcess.HasExited)
192192
{
193-
var stopScript = $@"do{{
193+
if (OperatingSystem.IsWindows())
194+
{
195+
LaunchStopScriptWindows(_spaProcess.Id);
196+
}
197+
else if (OperatingSystem.IsMacOS())
198+
{
199+
LaunchStopScriptMacOS(_spaProcess.Id);
200+
}
201+
}
202+
}
203+
catch (Exception exception)
204+
{
205+
_logger.LogError(exception, $"Failed to launch the SPA development server '{_options.LaunchCommand}'.");
206+
}
207+
}
208+
209+
private void LaunchStopScriptWindows(int spaProcessId)
210+
{
211+
var stopScript = $@"do{{
194212
try
195213
{{
196214
$processId = Get-Process -PID {Environment.ProcessId} -ErrorAction Stop;
@@ -203,35 +221,81 @@ private void LaunchDevelopmentProxy()
203221
204222
try
205223
{{
206-
taskkill /T /F /PID {_spaProcess.Id};
224+
taskkill /T /F /PID {spaProcessId};
207225
}}
208226
catch
209227
{{
210228
}}";
211-
var stopScriptInfo = new ProcessStartInfo(
212-
"powershell.exe",
213-
string.Join(" ", "-NoProfile", "-C", stopScript))
214-
{
215-
CreateNoWindow = true,
216-
WorkingDirectory = Path.Combine(AppContext.BaseDirectory, _options.WorkingDirectory)
217-
};
229+
var stopScriptInfo = new ProcessStartInfo(
230+
"powershell.exe",
231+
string.Join(" ", "-NoProfile", "-C", stopScript))
232+
{
233+
CreateNoWindow = true,
234+
WorkingDirectory = Path.Combine(AppContext.BaseDirectory, _options.WorkingDirectory)
235+
};
218236

219-
var stopProcess = Process.Start(stopScriptInfo);
220-
if (stopProcess == null || stopProcess.HasExited)
221-
{
222-
_logger.LogWarning($"SPA process shutdown script '{stopProcess?.Id}' failed to start. The SPA proxy might" +
223-
$" remain open if the dotnet process is terminated abruptly. Use the operating system command to kill" +
224-
$"the process tree for {_spaProcess.Id}");
225-
}
226-
else
227-
{
228-
_logger.LogDebug($"Watch process '{stopProcess}' started.");
229-
}
230-
}
237+
var stopProcess = Process.Start(stopScriptInfo);
238+
if (stopProcess == null || stopProcess.HasExited)
239+
{
240+
_logger.LogWarning($"The SPA process shutdown script '{stopProcess?.Id}' failed to start. The SPA proxy might" +
241+
$" remain open if the dotnet process is terminated ungracefully. Use the operating system commands to kill" +
242+
$" the process tree for {spaProcessId}");
231243
}
232-
catch (Exception exception)
244+
else
233245
{
234-
_logger.LogError(exception, $"Failed to launch the SPA development server '{_options.LaunchCommand}'.");
246+
_logger.LogDebug($"Watch process '{stopProcess}' started.");
247+
}
248+
}
249+
250+
private void LaunchStopScriptMacOS(int spaProcessId)
251+
{
252+
var fileName = Guid.NewGuid().ToString("N") + ".sh";
253+
var scriptPath = Path.Combine(AppContext.BaseDirectory, fileName);
254+
var stopScript = @$"function list_child_processes(){{
255+
local ppid=$1;
256+
local current_children=$(pgrep -P $ppid);
257+
local local_child;
258+
if [ $? -eq 0 ];
259+
then
260+
for current_child in $current_children
261+
do
262+
local_child=$current_child;
263+
list_child_processes $local_child;
264+
echo $local_child;
265+
done;
266+
else
267+
return 0;
268+
fi;
269+
}}
270+
271+
ps {Environment.ProcessId};
272+
while [ $? -eq 0 ];
273+
do
274+
sleep 1;
275+
ps {Environment.ProcessId} > /dev/null;
276+
done;
277+
278+
for child in $(list_child_processes {spaProcessId});
279+
do
280+
echo killing $child;
281+
kill -s KILL $child;
282+
done;
283+
rm {scriptPath};
284+
";
285+
File.WriteAllText(scriptPath, stopScript);
286+
287+
var stopScriptInfo = new ProcessStartInfo("/bin/bash", scriptPath)
288+
{
289+
CreateNoWindow = true,
290+
WorkingDirectory = Path.Combine(AppContext.BaseDirectory, _options.WorkingDirectory)
291+
};
292+
293+
var stopProcess = Process.Start(stopScriptInfo);
294+
if (stopProcess == null || stopProcess.HasExited)
295+
{
296+
_logger.LogWarning($"The SPA process shutdown script '{stopProcess?.Id}' failed to start. The SPA proxy might" +
297+
$" remain open if the dotnet process is terminated ungracefully. Use the operating system commands to kill" +
298+
$" the process tree for {spaProcessId}");
235299
}
236300
}
237301

0 commit comments

Comments
 (0)