IHostedService иногда вызывает System.ObjectDisposedException во время выполнения теста - PullRequest
0 голосов
/ 29 января 2020

Мы наблюдаем некоторые странные, на первый взгляд, случайные исключения, которые появляются на нашем сервере сборки во время наших тестовых прогонов do tnet.

Он всегда связан с указанным c IHostedService, который отправляется в базу данных и очищает записи с интервалом в 60 секунд:

public class SessionCleanupHostedService : IHostedService, IDisposable
{
    /*Constructor, fields and properties*/

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Session cleanup started. All expired sessions will be removed");
        _timer = new Timer(CleanUp, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
        return Task.CompletedTask;
    }

    private void CleanUp(object state)
    {
        //Singletons need to create their own scope, so we can access scoped services.
        using(var scope = Services?.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<ApplicationContext>();
            var sessionsToDelete = context.UserSession.Where(us => us.ExpiresAt < DateTime.UtcNow);
            context.UserSession.RemoveRange(sessionsToDelete);
            context.SaveChanges();
        }
    }
    /*StopAsync removed */

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _timer?.Dispose();
        }
    }
}

Ошибка возникает в using Statement using(var scope = Services.CreateScope())

Это то, что появляется в журналах:

The active test run was aborted. Reason: Unhandled Exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Namespace.SessionCleanupHostedService.CleanUp(Object state) in X:\s\Folders\Auth\SessionCleanupHostedService.cs:line 35

Я извлек ветку, которая вызывает эти исключения при сборке, и попытался воспроизвести это локально, но исключение просто не сработает.

Теоретически, нам не понадобится этот сервис во время тестирования, так как мы тестируем данные в памяти. Поэтому я пошел дальше и удалил его из `IntegrationTestBase, который предоставляет экземпляр HttpClient для наших тестов.

protected HttpClient GetClient()
{
    var client = _factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureTestServices(services =>
        {
            var descriptor = services.SingleOrDefault(
                d => d.ServiceType == typeof(IHostedService) &&
                d.ImplementationType.Name == "SessionCleanupHostedService");

            if (descriptor != null)
            {
                services.Remove(descriptor);
            }
            /* removed for brevity */
        });
    });
    /*some more things removed*/
    return client;
}

Теперь это, скорее всего, избавит от исключений. Нет топлива, нет огня. Но это не объяснит причину этого поведения. Я бы предпочел сохранить эту услугу и найти решение проблемы. Самое простое исправление, о котором я сейчас могу подумать, - добавить пустую проверку перед созданием:

...