-
Notifications
You must be signed in to change notification settings - Fork 417
Expand file tree
/
Copy pathProcessTerminationHandler.cs
More file actions
103 lines (86 loc) · 3.47 KB
/
ProcessTerminationHandler.cs
File metadata and controls
103 lines (86 loc) · 3.47 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
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace System.CommandLine.Invocation;
internal sealed class ProcessTerminationHandler : IDisposable
{
private const int SIGINT_EXIT_CODE = 130;
private const int SIGTERM_EXIT_CODE = 143;
internal readonly TaskCompletionSource<int> ProcessTerminationCompletionSource;
private readonly CancellationTokenSource _handlerCancellationTokenSource;
private Task<int>? _startedHandler;
private readonly TimeSpan _processTerminationTimeout;
#if NET7_0_OR_GREATER
private readonly IDisposable? _sigIntRegistration, _sigTermRegistration;
#endif
internal Task<int> StartedHandler { set => Volatile.Write(ref _startedHandler, value); }
internal ProcessTerminationHandler(
CancellationTokenSource handlerCancellationTokenSource,
TimeSpan processTerminationTimeout)
{
ProcessTerminationCompletionSource = new ();
_handlerCancellationTokenSource = handlerCancellationTokenSource;
_processTerminationTimeout = processTerminationTimeout;
#if NET7_0_OR_GREATER // we prefer the new API as they allow for cancelling SIGTERM
if (!OperatingSystem.IsAndroid()
&& !OperatingSystem.IsIOS()
&& !OperatingSystem.IsTvOS()
&& !OperatingSystem.IsBrowser())
{
_sigIntRegistration = PosixSignalRegistration.Create(PosixSignal.SIGINT, OnPosixSignal);
_sigTermRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, OnPosixSignal);
return;
}
#endif
Console.CancelKeyPress += OnCancelKeyPress;
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
}
public void Dispose()
{
#if NET7_0_OR_GREATER
if (_sigIntRegistration is not null)
{
_sigIntRegistration.Dispose();
_sigTermRegistration!.Dispose();
return;
}
#endif
Console.CancelKeyPress -= OnCancelKeyPress;
AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
}
#if NET7_0_OR_GREATER
void OnPosixSignal(PosixSignalContext context)
{
context.Cancel = true;
Cancel(context.Signal == PosixSignal.SIGINT ? SIGINT_EXIT_CODE : SIGTERM_EXIT_CODE);
}
#endif
void OnCancelKeyPress(object? sender, ConsoleCancelEventArgs e)
{
e.Cancel = true;
Cancel(SIGINT_EXIT_CODE);
}
void OnProcessExit(object? sender, EventArgs e) => Cancel(SIGTERM_EXIT_CODE);
void Cancel(int forcedTerminationExitCode)
{
// request cancellation
_handlerCancellationTokenSource.Cancel();
try
{
var startedHandler = Volatile.Read(ref _startedHandler);
// wait for the configured interval
if (startedHandler is null || !startedHandler.Wait(_processTerminationTimeout))
{
// if the handler does not finish within configured time,
// use the completion source to signal forced completion (preserving native exit code)
ProcessTerminationCompletionSource.SetResult(forcedTerminationExitCode);
}
}
catch (AggregateException)
{
// The task was cancelled or an exception was thrown during the task execution.
}
}
}