Skip to content

Propagate BackgroundService exceptions from the host#124863

Draft
svick wants to merge 2 commits intodotnet:mainfrom
svick:background-service-exceptions
Draft

Propagate BackgroundService exceptions from the host#124863
svick wants to merge 2 commits intodotnet:mainfrom
svick:background-service-exceptions

Conversation

@svick
Copy link
Member

@svick svick commented Feb 25, 2026

Fixes #67146 by throwing from IHost.RunAsync/StopAsync when a BackgroundService fails with an exception and BackgroundServiceExceptionBehavior.StopHost is set.

I think this should be documented as a breaking change, since users could be relying on having a throwing BackgroundService that stops the application but returns with success exit code.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes issue #67146 by ensuring that exceptions thrown asynchronously by BackgroundServices propagate to the caller and result in a non-zero exit code, matching the behavior when exceptions are thrown synchronously.

Changes:

  • Added exception tracking and propagation for background services in StopHost behavior mode
  • Replaced trivial enum tests with comprehensive functional tests covering various exception scenarios
  • Added logging for exception propagation

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs Added _backgroundServiceExceptions list to track background service exceptions and propagate them at the end of StopAsync(), ensuring non-zero exit codes
src/libraries/Microsoft.Extensions.Hosting/src/Internal/HostingLoggerExtensions.cs Added BackgroundServiceExceptionsPropagating logging method and fixed signature of StoppedWithException to require non-null exception
src/libraries/Microsoft.Extensions.Hosting/src/Internal/LoggerEventIds.cs Added BackgroundServiceExceptionsPropagating event ID
src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/BackgroundServiceExceptionTests.cs Added comprehensive tests for synchronous/asynchronous exceptions, multiple exceptions, and different exception behaviors
src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/BackgroundServiceExceptionBehaviorTests.cs Removed trivial enum value tests in favor of functional tests
Comments suppressed due to low confidence (6)

src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:146

  • The comment change from "Call" to "Cancel" is inaccurate. While NotifyStarted() internally cancels a CancellationTokenSource to signal the ApplicationStarted event, the comment should describe the semantic action (calling/triggering the notification), not the internal implementation detail. The original wording "Call IHostApplicationLifetime.Started" better describes what the code is doing at this level of abstraction.
                // Cancel IHostApplicationLifetime.Started

src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:239

  • The comment change from "Call" to "Cancel" is inaccurate. While StopApplication() internally cancels a CancellationTokenSource to signal the ApplicationStopping event, the comment should describe the semantic action (calling/triggering the event), not the internal implementation detail. The original wording "Call IHostApplicationLifetime.ApplicationStopping" better describes what the code is doing at this level of abstraction.
                    // Cancel IHostApplicationLifetime.ApplicationStopping.

src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:259

  • The comment change from "Call" to "Cancel" is inaccurate. While StopApplication() internally cancels a CancellationTokenSource to signal the ApplicationStopping event, the comment should describe the semantic action (calling/triggering the event), not the internal implementation detail. The original wording "Call IHostApplicationLifetime.ApplicationStopping" better describes what the code is doing at this level of abstraction.
                    // Cancel IHostApplicationLifetime.ApplicationStopping.

src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:275

  • The comment change from "Call" to "Cancel" is inaccurate. While NotifyStopped() internally cancels a CancellationTokenSource to signal the ApplicationStopped event, the comment should describe the semantic action (calling/triggering the notification), not the internal implementation detail. The original wording "Call IHostApplicationLifetime.Stopped" better describes what the code is doing at this level of abstraction.
                // Cancel IHostApplicationLifetime.Stopped

src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/BackgroundServiceExceptionTests.cs:82

  • The hardcoded 500ms delay creates a race condition and could lead to flaky tests. Consider using ApplicationStopping.WaitHandle.WaitOne (as seen in HostTests.cs line 710) or TaskCompletionSource patterns (as seen in HostTests.cs lines 713-758) for more reliable synchronization. These approaches ensure the background service has actually failed before calling StopAsync, rather than relying on timing assumptions.
            await Task.Delay(TimeSpan.FromMilliseconds(500));

src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/BackgroundServiceExceptionTests.cs:163

  • The hardcoded 2-second delay is longer than necessary and slows down test execution. Consider using ApplicationStopping.WaitHandle.WaitOne or TaskCompletionSource patterns for more efficient and reliable synchronization, as demonstrated in existing tests in HostTests.cs.
            await Task.Delay(TimeSpan.FromSeconds(2));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Different Exit code when IHost stops from failing (async)IHostedService

2 participants