You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
WebApplicationFactory sometimes throws ObjectDisposedException instead of the app's startup exception #69684
When the app under test fails in IHost.StartAsync (for example ValidateOnStart throwing OptionsValidationException), WebApplicationFactory<T>.CreateClient() is supposed to rethrow that exception. Intermittently it throws ObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider' from DeferredHostBuilder.DeferredHost.StartAsync instead, and the real startup error is lost.
It's a race between two threads:
HostFactoryResolver runs the app's entry point on its own thread with stopApplication: false. builder.Build() hands the host to the factory, and the entry point carries on into app.Run().
StartAsync throws, and the finally in HostingAbstractionsHostExtensions.RunAsync disposes the host. After that, the entry point exits and DeferredHostBuilder.EntryPointCompleted(exception) faults the start TCS with the real exception.
Meanwhile, on the test thread, DeferredHost.StartAsync calls _host.Services.GetRequiredService<IHostApplicationLifetime>() to register on ApplicationStarted. If step 2 has already disposed the host, this throws ObjectDisposedException before the TCS is ever awaited.
In our CI it hits about 1 in 16 runs on busy x64 Linux agents, and never on an arm64 Mac.
Expected Behavior
CreateClient() consistently throws the exception the app failed to start with (OptionsValidationException here), as it already does when the app throws before Run().
Steps To Reproduce
Widening the window with an overridden CreateHost makes it fail every time:
// TestpublicclassStartupFailureTests{[Fact]publicvoidCreateClient_ThrowsStartupException(){usingvarfactory=newSlowStartFactory();Assert.Throws<OptionsValidationException>(()=>factory.CreateClient());// Actual: ObjectDisposedException}privatesealedclassSlowStartFactory:WebApplicationFactory<Program>{protectedoverrideIHostCreateHost(IHostBuilderbuilder){varhost=builder.Build();Thread.Sleep(500);// let the app thread fail and dispose the host firsthost.Start();returnhost;}}}
Without the override, the same test fails intermittently under CPU load.
Exceptions (if any)
System.ObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider'.
at ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.AspNetCore.Mvc.Testing.DeferredHostBuilder.DeferredHost.StartAsync(CancellationToken)
at HostingAbstractionsHostExtensions.Start(IHost host)
at WebApplicationFactory`1.CreateHost(IHostBuilder builder)
at WebApplicationFactory`1.ConfigureHostBuilder(IHostBuilder hostBuilder)
at WebApplicationFactory`1.StartServer()
at WebApplicationFactory`1.CreateClient()
.NET Version
10.0
Anything else?
ASP.NET Core version: Microsoft.AspNetCore.Mvc.Testing 10.0.12. DeferredHostBuilder is unchanged on main.
Related: Quarantine WebApplicationFactory #58442 reports the same ObjectDisposedException from other call sites (HostingListener.CreateHost, and the IServer lookup in WebApplicationFactory.ConfigureHostBuilder). The fix proposed here only covers the DeferredHost.StartAsync stack and doesn't address those.
Related: DeferredHostBuilder starts the host during the build action #38335. A July 2023 comment there reports this exact failure (ValidateOnStart failing in app.Run(), ObjectDisposedException at DeferredHostBuilder.cs line 152), worked around with test retries. That issue is about the broader, by-design behaviour of DeferredHostBuilder.Build() running the entry point on its own thread; the fix proposed here doesn't change that.
Proposed fix: in DeferredHost.StartAsync, if resolving IHostApplicationLifetime throws ObjectDisposedException, skip the ApplicationStarted registration and still await the start TCS. The host is only disposed once the entry point is on its way out, and HostFactoryResolver calls entrypointCompleted in a finally, so the TCS always completes with the app's exception (or with success). PR with a deterministic regression test: #69685.
Area:area-mvc (Microsoft.AspNetCore.Mvc.Testing — WebApplicationFactory / DeferredHostBuilder) Type:Bug (CreateClient throws ObjectDisposedException instead of the app's startup exception)
Potential Duplicates
Quarantine WebApplicationFactory #58442 - Quarantine WebApplicationFactory (similarity: medium; reports the same ObjectDisposedException: IServiceProvider from other call sites, as the reporter notes)
Is there an existing issue for this?
Describe the bug
When the app under test fails in
IHost.StartAsync(for exampleValidateOnStartthrowingOptionsValidationException),WebApplicationFactory<T>.CreateClient()is supposed to rethrow that exception. Intermittently it throwsObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider'fromDeferredHostBuilder.DeferredHost.StartAsyncinstead, and the real startup error is lost.It's a race between two threads:
HostFactoryResolverruns the app's entry point on its own thread withstopApplication: false.builder.Build()hands the host to the factory, and the entry point carries on intoapp.Run().StartAsyncthrows, and thefinallyinHostingAbstractionsHostExtensions.RunAsyncdisposes the host. After that, the entry point exits andDeferredHostBuilder.EntryPointCompleted(exception)faults the start TCS with the real exception.DeferredHost.StartAsynccalls_host.Services.GetRequiredService<IHostApplicationLifetime>()to register onApplicationStarted. If step 2 has already disposed the host, this throwsObjectDisposedExceptionbefore the TCS is ever awaited.In our CI it hits about 1 in 16 runs on busy x64 Linux agents, and never on an arm64 Mac.
Expected Behavior
CreateClient()consistently throws the exception the app failed to start with (OptionsValidationExceptionhere), as it already does when the app throws beforeRun().Steps To Reproduce
Widening the window with an overridden
CreateHostmakes it fail every time:Without the override, the same test fails intermittently under CPU load.
Exceptions (if any)
.NET Version
10.0
Anything else?
DeferredHostBuilderis unchanged onmain.ObjectDisposedExceptionfrom other call sites (HostingListener.CreateHost, and theIServerlookup inWebApplicationFactory.ConfigureHostBuilder). The fix proposed here only covers theDeferredHost.StartAsyncstack and doesn't address those.ValidateOnStartfailing inapp.Run(),ObjectDisposedExceptionatDeferredHostBuilder.csline 152), worked around with test retries. That issue is about the broader, by-design behaviour ofDeferredHostBuilder.Build()running the entry point on its own thread; the fix proposed here doesn't change that.Proposed fix: in
DeferredHost.StartAsync, if resolvingIHostApplicationLifetimethrowsObjectDisposedException, skip theApplicationStartedregistration and still await the start TCS. The host is only disposed once the entry point is on its way out, andHostFactoryResolvercallsentrypointCompletedin afinally, so the TCS always completes with the app's exception (or with success). PR with a deterministic regression test: #69685.