-
Notifications
You must be signed in to change notification settings - Fork 991
Expand file tree
/
Copy pathProcessSignaler.cs
More file actions
110 lines (97 loc) · 3.9 KB
/
Copy pathProcessSignaler.cs
File metadata and controls
110 lines (97 loc) · 3.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
/// <summary>
/// Provides best-effort process signaling for graceful shutdown and forceful termination.
/// </summary>
internal static partial class ProcessSignaler
{
public static void RequestGracefulShutdown(int pid, DateTimeOffset? expectedStartTime, ILogger logger)
{
using var process = TryGetRunningProcess(pid, expectedStartTime, logger);
if (process is null)
{
return; // Process is not running or does not match the expected start time
}
logger.LogDebug("Requesting graceful shutdown of process {Pid}...", pid);
if (OperatingSystem.IsWindows())
{
logger.LogDebug("Windows graceful process shutdown is handled by caller-specific process tree signaling.");
}
else
{
RequestGracefulShutdownUnix(pid, logger);
}
}
public static void ForceKill(int pid, DateTimeOffset? expectedStartTime, ILogger logger, bool killEntireProcessTree = false)
{
using var process = TryGetRunningProcess(pid, expectedStartTime, logger);
if (process is { })
{
logger.LogDebug("Killing process {Pid} (entireProcessTree={EntireProcessTree})...", pid, killEntireProcessTree);
try
{
process.Kill(entireProcessTree: killEntireProcessTree);
}
catch (InvalidOperationException)
{
// Process already exited.
}
}
}
public static Process? TryGetRunningProcess(int pid, DateTimeOffset? expectedStartTime, ILogger logger)
{
try
{
var process = Process.GetProcessById(pid);
if (expectedStartTime is not null && !AreClose(expectedStartTime, process.StartTime))
{
logger.LogDebug("Process {Pid} start time {ProcessStartTime} does not match expected start time {ExpectedStartTime}", pid, process.StartTime, expectedStartTime);
process.Dispose();
return null; // Do not return processes that do not match the expected start time
}
if (process.HasExited)
{
process.Dispose();
return null;
}
return process;
}
catch (ArgumentException)
{
// Process doesn't exist - already terminated.
return null;
}
catch (InvalidOperationException)
{
// Process has already exited.
return null;
}
}
private static bool AreClose(DateTimeOffset? expectedStartTime, DateTime processStartTime, TimeSpan? tolerance = default)
{
if (expectedStartTime is null)
{
return true;
}
tolerance ??= TimeSpan.FromSeconds(1);
return ((DateTimeOffset)expectedStartTime - new DateTimeOffset(processStartTime)).Duration() <= tolerance;
}
private const int SigTerm = 15;
private static void RequestGracefulShutdownUnix(int pid, ILogger logger)
{
var result = kill(pid, SigTerm);
if (result != 0)
{
int errno = Marshal.GetLastSystemError();
// Best effort.
logger.LogWarning("Could not gracefully stop Aspire application host process {Pid}; the error code from signal send operation was {ErrorCode}", pid, errno);
}
}
// "libc" here is a moniker for standard C library, which .NET maps to system C library on Unix-like systems.
// See https://developers.redhat.com/blog/2019/03/25/using-net-pinvoke-for-linux-system-functions
[LibraryImport("libc", SetLastError = true, EntryPoint = "kill")]
private static partial int kill(int pid, int sig);
}