Skip to content

Commit 161caf7

Browse files
authored
dotnet SIGTERM signal handler registration (#50983)
2 parents 99a5ce1 + 874a9ed commit 161caf7

File tree

4 files changed

+49
-17
lines changed

4 files changed

+49
-17
lines changed

src/Cli/dotnet/Program.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.CommandLine;
77
using System.CommandLine.Parsing;
88
using System.Diagnostics;
9+
using System.Runtime.InteropServices;
910
using Microsoft.DotNet.Cli.CommandFactory;
1011
using Microsoft.DotNet.Cli.CommandFactory.CommandResolution;
1112
using Microsoft.DotNet.Cli.Commands.Run;
@@ -29,6 +30,10 @@ public class Program
2930
public static ITelemetry TelemetryClient;
3031
public static int Main(string[] args)
3132
{
33+
// Register a handler for SIGTERM to allow graceful shutdown of the application on Unix.
34+
// See https://github.com/dotnet/docs/issues/46226.
35+
using var termSignalRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, _ => Environment.Exit(0));
36+
3237
using AutomaticEncodingRestorer _ = new();
3338

3439
// Setting output encoding is not available on those platforms

test/TestAssets/TestProjects/WatchHotReloadApp/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Runtime.Versioning;
99
using System.Threading;
1010
using System.Threading.Tasks;
11+
using System.Runtime.InteropServices;
1112

1213
// <metadata update handler placeholder>
1314

test/dotnet-watch.Tests/CommandLine/LaunchSettingsTests.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,22 +92,15 @@ public async Task RunsWithIterationEnvVariable()
9292

9393
App.Start(testAsset, []);
9494

95-
await App.AssertStarted();
95+
await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForFileChangeBeforeRestarting);
9696

97-
var source = Path.Combine(testAsset.Path, "Program.cs");
98-
var contents = File.ReadAllText(source);
99-
const string messagePrefix = "DOTNET_WATCH_ITERATION = ";
97+
App.AssertOutputContains("DOTNET_WATCH_ITERATION = 1");
98+
App.Process.ClearOutput();
10099

101-
var value = await App.AssertOutputLineStartsWith(messagePrefix);
102-
Assert.Equal(1, int.Parse(value, CultureInfo.InvariantCulture));
100+
UpdateSourceFile(Path.Combine(testAsset.Path, "Program.cs"));
103101

104102
await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForFileChangeBeforeRestarting);
105-
106-
UpdateSourceFile(source);
107-
await App.AssertStarted();
108-
109-
value = await App.AssertOutputLineStartsWith(messagePrefix);
110-
Assert.Equal(2, int.Parse(value, CultureInfo.InvariantCulture));
103+
App.AssertOutputContains("DOTNET_WATCH_ITERATION = 2");
111104
}
112105

113106
[Fact]

test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ public async Task AutoRestartOnRudeEdit(bool nonInteractive)
429429
await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges);
430430

431431
App.AssertOutputContains(MessageDescriptor.RestartNeededToApplyChanges);
432-
App.AssertOutputContains($"⌚ [auto-restart] {programPath}(38,11): error ENC0023: Adding an abstract method or overriding an inherited method requires restarting the application.");
432+
App.AssertOutputContains($"⌚ [auto-restart] {programPath}(39,11): error ENC0023: Adding an abstract method or overriding an inherited method requires restarting the application.");
433433
App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited");
434434
App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched");
435435
App.Process.ClearOutput();
@@ -459,7 +459,7 @@ public async Task AutoRestartOnRudeEditAfterRestartPrompt()
459459
await App.AssertOutputLineStartsWith(" ❔ Do you want to restart your app? Yes (y) / No (n) / Always (a) / Never (v)", failure: _ => false);
460460

461461
App.AssertOutputContains(MessageDescriptor.RestartNeededToApplyChanges);
462-
App.AssertOutputContains($"❌ {programPath}(38,11): error ENC0023: Adding an abstract method or overriding an inherited method requires restarting the application.");
462+
App.AssertOutputContains($"❌ {programPath}(39,11): error ENC0023: Adding an abstract method or overriding an inherited method requires restarting the application.");
463463
App.Process.ClearOutput();
464464

465465
App.SendKey('a');
@@ -476,7 +476,7 @@ public async Task AutoRestartOnRudeEditAfterRestartPrompt()
476476
await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges);
477477

478478
App.AssertOutputContains(MessageDescriptor.RestartNeededToApplyChanges);
479-
App.AssertOutputContains($"⌚ [auto-restart] {programPath}(38,1): error ENC0033: Deleting method 'F()' requires restarting the application.");
479+
App.AssertOutputContains($"⌚ [auto-restart] {programPath}(39,1): error ENC0033: Deleting method 'F()' requires restarting the application.");
480480
App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited");
481481
App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched");
482482
}
@@ -514,7 +514,7 @@ public async Task AutoRestartOnNoEffectEdit(bool nonInteractive)
514514
await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges);
515515

516516
App.AssertOutputContains(MessageDescriptor.RestartNeededToApplyChanges);
517-
App.AssertOutputContains($"⌚ [auto-restart] {programPath}(16,19): warning ENC0118: Changing 'top-level code' might not have any effect until the application is restarted.");
517+
App.AssertOutputContains($"⌚ [auto-restart] {programPath}(17,19): warning ENC0118: Changing 'top-level code' might not have any effect until the application is restarted.");
518518
App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited");
519519
App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched");
520520
App.AssertOutputContains("<Updated>");
@@ -721,6 +721,8 @@ class AppUpdateHandler
721721
[PlatformSpecificFact(TestPlatforms.Windows)]
722722
public async Task GracefulTermination_Windows()
723723
{
724+
var tfm = ToolsetInfo.CurrentTargetFramework;
725+
724726
var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp")
725727
.WithSource();
726728

@@ -739,7 +741,7 @@ public async Task GracefulTermination_Windows()
739741

740742
await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges);
741743

742-
await App.WaitUntilOutputContains(new Regex(@"dotnet watch 🕵️ \[.*\] Windows Ctrl\+C handling enabled."));
744+
await App.WaitUntilOutputContains($"dotnet watch 🕵️ [WatchHotReloadApp ({tfm})] Windows Ctrl+C handling enabled.");
743745

744746
await App.WaitUntilOutputContains("Started");
745747

@@ -749,6 +751,37 @@ public async Task GracefulTermination_Windows()
749751
await App.WaitUntilOutputContains("exited with exit code 0.");
750752
}
751753

754+
[PlatformSpecificFact(TestPlatforms.AnyUnix)]
755+
public async Task GracefulTermination_Unix()
756+
{
757+
var tfm = ToolsetInfo.CurrentTargetFramework;
758+
759+
var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp")
760+
.WithSource();
761+
762+
var programPath = Path.Combine(testAsset.Path, "Program.cs");
763+
764+
UpdateSourceFile(programPath, src => src.Replace("// <metadata update handler placeholder>", """
765+
using var termSignalRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, _ =>
766+
{
767+
Console.WriteLine("SIGTERM detected! Performing cleanup...");
768+
});
769+
"""));
770+
771+
App.Start(testAsset, [], testFlags: TestFlags.ReadKeyFromStdin);
772+
773+
await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges);
774+
775+
await App.WaitUntilOutputContains($"dotnet watch 🕵️ [WatchHotReloadApp ({tfm})] Posix signal handlers registered.");
776+
777+
await App.WaitUntilOutputContains("Started");
778+
779+
App.SendControlC();
780+
781+
await App.WaitForOutputLineContaining("SIGTERM detected! Performing cleanup...");
782+
await App.WaitUntilOutputContains("exited with exit code 0.");
783+
}
784+
752785
[PlatformSpecificTheory(TestPlatforms.Windows, Skip = "https://github.com/dotnet/sdk/issues/49928")] // https://github.com/dotnet/aspnetcore/issues/63759
753786
[CombinatorialData]
754787
public async Task BlazorWasm(bool projectSpecifiesCapabilities)

0 commit comments

Comments
 (0)