Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't log an error when a BackgroundService is canceled due to the host being stopped. #57005

Merged
merged 6 commits into from
Aug 10, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 50 additions & 34 deletions src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal sealed class Host : IHost, IAsyncDisposable
private readonly IHostEnvironment _hostEnvironment;
private readonly PhysicalFileProvider _defaultProvider;
private IEnumerable<IHostedService> _hostedServices;
private bool _stopping;
eerhardt marked this conversation as resolved.
Show resolved Hide resolved

public Host(IServiceProvider services,
IHostEnvironment hostEnvironment,
Expand Down Expand Up @@ -84,6 +85,13 @@ private async Task TryExecuteBackgroundServiceAsync(BackgroundService background
}
catch (Exception ex)
{
// 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 (_stopping && backgroundService.ExecuteTask.IsCanceled && ex is OperationCanceledException)
{
return;
}

_logger.BackgroundServiceFaulted(ex);
if (_options.BackgroundServiceExceptionBehavior == BackgroundServiceExceptionBehavior.StopHost)
{
Expand All @@ -95,52 +103,60 @@ private async Task TryExecuteBackgroundServiceAsync(BackgroundService background

public async Task StopAsync(CancellationToken cancellationToken = default)
{
_logger.Stopping();

using (var cts = new CancellationTokenSource(_options.ShutdownTimeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
try
{
CancellationToken token = linkedCts.Token;
// Trigger IHostApplicationLifetime.ApplicationStopping
_applicationLifetime.StopApplication();
_stopping = true;
_logger.Stopping();

IList<Exception> exceptions = new List<Exception>();
if (_hostedServices != null) // Started?
using (var cts = new CancellationTokenSource(_options.ShutdownTimeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
{
foreach (IHostedService hostedService in _hostedServices.Reverse())
CancellationToken token = linkedCts.Token;
// Trigger IHostApplicationLifetime.ApplicationStopping
_applicationLifetime.StopApplication();

IList<Exception> exceptions = new List<Exception>();
if (_hostedServices != null) // Started?
{
try
foreach (IHostedService hostedService in _hostedServices.Reverse())
{
await hostedService.StopAsync(token).ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.Add(ex);
try
{
await hostedService.StopAsync(token).ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
}
}

// Fire IHostApplicationLifetime.Stopped
_applicationLifetime.NotifyStopped();
// Fire IHostApplicationLifetime.Stopped
_applicationLifetime.NotifyStopped();

try
{
await _hostLifetime.StopAsync(token).ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
try
{
await _hostLifetime.StopAsync(token).ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.Add(ex);
}

if (exceptions.Count > 0)
{
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
_logger.StoppedWithException(ex);
throw ex;
if (exceptions.Count > 0)
{
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
_logger.StoppedWithException(ex);
throw ex;
}
}
}

_logger.Stopped();
_logger.Stopped();
}
finally
{
_stopping = false;
}
}

public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,45 @@ public async Task BackgroundServiceAsyncExceptionGetsLogged(
}
}

/// <summary>
/// Tests that when a BackgroundService is canceled when stopping the host,
/// no error is logged.
/// </summary>
[Fact]
public async Task HostNoErrorWhenServiceIsCanceledAsPartOfStop()
{
using TestEventListener listener = new TestEventListener();
eerhardt marked this conversation as resolved.
Show resolved Hide resolved

using IHost host = CreateBuilder()
.ConfigureLogging(logging =>
{
logging.AddEventSourceLogger();
})
.ConfigureServices(services =>
{
services.AddHostedService<AsyncWaitingService>();
})
.Build();

host.Start();
await host.StopAsync();

EventWrittenEventArgs[] events =
listener.EventData.Where(
e => e.EventSource.Name == "Microsoft-Extensions-Logging").ToArray();

foreach (EventWrittenEventArgs eventArg in events)
{
int levelIndex = eventArg.PayloadNames.IndexOf("Level");
LogLevel level = (LogLevel)eventArg.Payload[levelIndex];
Assert.True(level < LogLevel.Error);

int eventNameIndex = eventArg.PayloadNames.IndexOf("EventName");
string eventName = (string)eventArg.Payload[eventNameIndex];
Assert.NotEqual("BackgroundServiceFaulted", eventName);
}
}

private IHostBuilder CreateBuilder(IConfiguration config = null)
{
return new HostBuilder().ConfigureHostConfiguration(builder => builder.AddConfiguration(config ?? new ConfigurationBuilder().Build()));
Expand Down Expand Up @@ -1513,5 +1552,18 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
throw new Exception("Background Exception");
}
}

private class AsyncWaitingService : BackgroundService
{
public AsyncWaitingService() { }

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(1000, stoppingToken);
}
eerhardt marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}