From 2329125d7f76c8e9c0f8bc1d6c9b4477900fde58 Mon Sep 17 00:00:00 2001 From: Christopher Prosser <4368756+ProsserCJ@users.noreply.github.com> Date: Wed, 20 Mar 2024 18:37:02 +0000 Subject: [PATCH] Don't log error from background service if host is gracefully stopped while still starting Fixes #98935 --- .../src/Internal/Host.cs | 4 +- .../tests/UnitTests/Internal/HostTests.cs | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs b/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs index 154b4225b00245..e84d2f3a646215 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs @@ -29,7 +29,6 @@ internal sealed class Host : IHost, IAsyncDisposable private IEnumerable? _hostedServices; private IEnumerable? _hostedLifecycleServices; private bool _hostStarting; - private volatile bool _stopCalled; private bool _hostStopped; public Host(IServiceProvider services, @@ -190,7 +189,7 @@ private async Task TryExecuteBackgroundServiceAsync(BackgroundService background { // When the host is being stopped, it cancels the background services. // This isn't an error condition, so don't log it as an error. - if (_stopCalled && backgroundTask.IsCanceled && ex is OperationCanceledException) + if (_applicationLifetime.ApplicationStopping.IsCancellationRequested && backgroundTask.IsCanceled && ex is OperationCanceledException) { return; } @@ -217,7 +216,6 @@ private async Task TryExecuteBackgroundServiceAsync(BackgroundService background /// public async Task StopAsync(CancellationToken cancellationToken = default) { - _stopCalled = true; _logger.Stopping(); CancellationTokenSource? cts = null; diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.cs index 23cf84e91621d3..64f9dd8f904118 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.cs @@ -1487,6 +1487,39 @@ public async Task StartOnBackgroundServiceThatDoesNotCallBase() } } + /// + /// Tests that when a BackgroundService is cancelled when stopping a host which has not finished starting, it does not log an error + /// + [Fact] + public async Task HostNoErrorWhenStartingServiceIsCanceledAsPartOfStop() + { + TestLoggerProvider logger = new TestLoggerProvider(); + + using IHost host = CreateBuilder() + .ConfigureLogging(logging => + { + logging.AddProvider(logger); + }) + .ConfigureServices(services => + { + services.AddHostedService(); + services.AddHostedService(); + }) + .Build(); + + IHostApplicationLifetime lifetime = host.Services.GetRequiredService(); + _ = host.StartAsync(); + lifetime.StopApplication(); + await Task.Delay(TimeSpan.FromMilliseconds(100)); + await host.WaitForShutdownAsync(); + + foreach (LogEvent logEvent in logger.GetEvents()) + { + Assert.True(logEvent.LogLevel < LogLevel.Error); + Assert.NotEqual("BackgroundServiceFaulted", logEvent.EventId.Name); + } + } + private IHostBuilder CreateBuilder(IConfiguration config = null) { return new HostBuilder().ConfigureHostConfiguration(builder => builder.AddConfiguration(config ?? new ConfigurationBuilder().Build())); @@ -1637,5 +1670,15 @@ private class BackgroundServiceDoesNotCallBase : BackgroundService public override Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } + + private class SlowStartService : IHostedService + { + public async Task StartAsync(CancellationToken cancellationToken) + { + await Task.Delay(TimeSpan.FromSeconds(10), CancellationToken.None); + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } } }