Skip to content

WebApplicationFactory disposes host before StopAsync on background service is called #57738

Open
@lostmsu

Description

@lostmsu

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Please see the description from #50622

TL;DR; when deriving a BackgroundService and overriding StopAsync this fails with ObjectDisposedException for serviceProvider during integration testing:

public override async Task StopAsync(CancellationToken token)
{
  await base.StopAsync(token);

  using var scope = serviceProvider.CreateScope();
}

Where serviceProvider was injected via constructor.

Expected Behavior

I should be able to resolve services from DI until my stopping is done.

Steps To Reproduce

See full repro here: https://github.com/lostmsu/ServiceProviderDisposedRepro (upgraded to .NET 8)

Exceptions (if any)

Class cleanup failed in ServiceProviderDisposed.Tests.Tests

System.ObjectDisposedException
Cannot access a disposed object.
Object name: 'IServiceProvider'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.CreateScope(IServiceProvider provider)
   at ServiceProviderDisposed.DummyBackgroundService.StopAsync(CancellationToken cancellationToken) in C:\Users\brome\Projects\Play\Bugs\ServiceProviderDisposedRepro\ServiceProviderDisposed\DummyBackgroundService.cs:line 24
   at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
   at Microsoft.Extensions.Hosting.Internal.Host.StopAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.DisposeAsync()
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.Dispose(Boolean disposing)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.Dispose()
   at Xunit.Sdk.ExceptionAggregator.Run(Action code) in /_/src/xunit.core/Sdk/ExceptionAggregator.cs:line 73

.NET Version

8.0.401

Anything else?

The original bug report suggested to implement IHostedLifecycleService, but moving

using var scope = serviceProvider.CreateScope();

to StoppedAsync does not prevent the issue.

The scenario I have in mind is to do something after my main service procedure ends (e.g. finalize database recording).

Workaround

Just found a workaround: you can move the necessary code at the end of

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        await Task.Delay(50);
    }
    DoYourThingHere();
}

The problem with that though is that you lose access to the CancellationToken from StopAsync: the one in ExecuteAsync has completely different purpose.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesfeature-mvc-testingMVC testing package

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions