Skip to content

Commit 042a5cc

Browse files
committed
Do not call ServiceBase.Stop(), fix some suppressions.
1 parent 168cd82 commit 042a5cc

5 files changed

Lines changed: 44 additions & 36 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// This file is used by Code Analysis to maintain SuppressMessage
2+
// attributes that are applied to this project.
3+
// Project-level suppressions either have no target or are given
4+
// a specific target and scoped to a namespace, type, member, etc.
5+
6+
using System.Diagnostics.CodeAnalysis;
7+
8+
[assembly: SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "We're a Windows Service, UseWindowsServiceExtensions() checks this", Scope = "namespaceanddescendants", Target = "~N:CodeCaster.WindowsServiceExtensions")]

src/CodeCaster.WindowsServiceExtensions/Lifetime/ExtendedWindowsServiceLifetime.cs

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,11 @@ public class ExtendedWindowsServiceLifetime : HostApplicationStartupLifetime
2323
public ExtendedWindowsServiceLifetime(IServiceProvider serviceProvider, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions<HostOptions> optionsAccessor)
2424
: base(serviceProvider, environment, applicationLifetime, loggerFactory, optionsAccessor)
2525
{
26-
// Should not happen, here to keep the code analysis happy and the intention explicit.
27-
#pragma warning disable CA1416 // Validate platform compatibility
28-
if (!OperatingSystem.IsWindows() || !WindowsServiceHelpers.IsWindowsService())
26+
if (OperatingSystem.IsWindows() && WindowsServiceHelpers.IsWindowsService())
2927
{
30-
const string methodName = nameof(WindowsServiceLifetimeHostBuilderExtensionsAdapter.UseWindowsServiceExtensions);
31-
32-
throw new PlatformNotSupportedException($"Windows Service needs to run on Windows. Remove the call to {methodName}()");
28+
CanHandlePowerEvent = true;
29+
CanHandleSessionChangeEvent = true;
3330
}
34-
35-
// Explicitly stop the service instead of just exiting the process.
36-
// But this this will kill the application quickly, do other BackgroundServices get the time to exit nicely?
37-
applicationLifetime.ApplicationStopping.Register(() =>
38-
{
39-
Logger.LogInformation("Application stopping, stopping service, exit code: {exitCode}", ExitCode);
40-
41-
Stop();
42-
});
43-
44-
CanHandlePowerEvent = true;
45-
CanHandleSessionChangeEvent = true;
4631
}
4732

4833
/// <summary>
@@ -80,19 +65,19 @@ protected override void OnSessionChange(SessionChangeDescription changeDescripti
8065
if (_hostedServices == null)
8166
{
8267
Logger.LogDebug("No hosted services, returning");
83-
68+
8469
return;
8570
}
8671

8772
// Forward the event to all registered I(PowerEventAware)HostedServices.
8873
foreach (var service in _hostedServices)
8974
{
90-
Logger.LogDebug("Notifying service {service} about session change event: {sessionId}, {reason}",
91-
service.GetType().FullName,
92-
changeDescription.SessionId,
75+
Logger.LogDebug("Notifying service {service} about session change event: {sessionId}, {reason}",
76+
service.GetType().FullName,
77+
changeDescription.SessionId,
9378
changeDescription.Reason.ToString()
9479
);
95-
80+
9681
service.OnSessionChange(changeDescription);
9782
}
9883
}
@@ -106,15 +91,15 @@ protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
10691
if (_hostedServices == null)
10792
{
10893
Logger.LogDebug("No hosted services, returning");
109-
94+
11095
return true;
11196
}
11297

11398
// Forward the event to all registered I(PowerEventAware)HostedServices.
11499
foreach (var service in _hostedServices)
115100
{
116101
Logger.LogDebug("Notifying service {service} about power event: {powerStatus}", service.GetType().FullName, powerStatus);
117-
102+
118103
service.OnPowerEvent(powerStatus);
119104
}
120105

@@ -123,4 +108,3 @@ protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
123108
}
124109
}
125110
}
126-
#pragma warning restore CA1416 // Validate platform compatibility - constructor handles that

src/CodeCaster.WindowsServiceExtensions/Lifetime/HostApplicationStartupLifetime.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,19 @@ public HostApplicationStartupLifetime(IServiceProvider serviceProvider, IHostEnv
7171
}
7272
}
7373

74+
/// <summary>
75+
/// Do not call ServiceBase.Stop() on error.
76+
/// </summary>
77+
public new Task StopAsync(CancellationToken cancellationToken)
78+
{
79+
if (!OperatingSystem.IsWindows() || !WindowsServiceHelpers.IsWindowsService() || ExitCode == 0)
80+
{
81+
base.StopAsync(cancellationToken);
82+
}
83+
84+
return Task.CompletedTask;
85+
}
86+
7487
/// <summary>
7588
/// Override to execute startup code, no need to call base. Async because who knows.
7689
/// </summary>

src/CodeCaster.WindowsServiceExtensions/Service/WindowsServiceBackgroundService.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ protected sealed override async Task ExecuteAsync(CancellationToken stoppingToke
6565
}
6666
catch (Exception)
6767
{
68-
Logger.LogDebug("Setting process exit code to {exitCode}", ErrorInvalidData);
68+
Logger.LogInformation("Exception occurred, setting process exit code to {exitCode}", ErrorInvalidData);
6969

7070
// The .NET host will shut down with code 0 if we don't do this.
7171
Environment.ExitCode = ErrorInvalidData;
@@ -76,11 +76,12 @@ protected sealed override async Task ExecuteAsync(CancellationToken stoppingToke
7676
Logger.LogDebug("Setting service exit code to {exitCode}", ErrorInvalidData);
7777

7878
// To report to the Service Control Manager on failure, is uint so > 0.
79-
ServiceLifetime.ExitCode = ErrorInvalidData;
79+
ServiceLifetime.ExitCode = ErrorInvalidData + 1;
8080
}
8181
#pragma warning restore CA1416 // Validate platform compatibility
8282

83-
// Let the BackgroundService handle the exception.
83+
// Let the BackgroundService handle and log the exception.
84+
// Do _not_ call ServiceBase.Stop(), or it'll think we exited successfully.
8485
throw;
8586
}
8687
}

test/TestServiceThatThrows/Program.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ namespace TestServiceThatThrows
1212
{
1313
public static class Program
1414
{
15+
// Fake doing at least a second's work to not throw immediately...
16+
public static int SecondsToWaitBeforeThrowing;
17+
1518
public static async Task Main()
1619
{
1720
// Hint: Reattach to Process (Shift+Alt+P)
@@ -22,6 +25,8 @@ public static async Task Main()
2225
Debugger.Break();
2326
}
2427

28+
SecondsToWaitBeforeThrowing = Debugger.IsAttached ? 1 : 31;
29+
2530
await new HostBuilder()
2631
.ConfigureLogging(l => l.AddConsole())
2732
.ConfigureServices((s) =>
@@ -45,7 +50,7 @@ public static async Task Main()
4550
// Should give startup error.
4651
//throw new InvalidOperationException("Heh");
4752
})
48-
.UseWindowsService()
53+
//.UseWindowsService()
4954
.UseWindowsServiceExtensions()
5055
//.UseWindowsServiceExtensions(o =>
5156
//{
@@ -83,7 +88,7 @@ public class MyFaultyService : IHostedService
8388
{
8489
public async Task StartAsync(CancellationToken cancellationToken)
8590
{
86-
await Task.Delay(5000, cancellationToken);
91+
await Task.Delay(Program.SecondsToWaitBeforeThrowing * 1000, cancellationToken);
8792

8893
// This works, kills the host (awaits startup).
8994
throw new InvalidOperationException("This service is not supposed to start");
@@ -107,7 +112,7 @@ public MyFaultyBackgroundService(ILogger<MyFaultyBackgroundService> logger)
107112
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
108113
{
109114
// Fake doing at least some work...
110-
await Task.Delay(500, stoppingToken);
115+
await Task.Delay(Program.SecondsToWaitBeforeThrowing * 1000, stoppingToken);
111116

112117
_logger.LogError("This service is not supposed to start, but this error won't kill the host");
113118

@@ -130,10 +135,7 @@ protected override async Task TryExecuteAsync(CancellationToken stoppingToken)
130135
{
131136
Logger.LogInformation("Sleeping, then throwing");
132137

133-
// Fake doing at least a second's work to not throw immediately...
134-
int secondsToWait = Debugger.IsAttached ? 1 : 31;
135-
136-
await Task.Delay(secondsToWait * 1000, stoppingToken);
138+
await Task.Delay(Program.SecondsToWaitBeforeThrowing * 1000, stoppingToken);
137139

138140
// This will now stop the host application.
139141
throw new InvalidOperationException("This service is not supposed to start");

0 commit comments

Comments
 (0)