WebApplicationFactory sometimes throws ObjectDisposedException instead of the app's startup exception
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 85/100
Research direction
The bug resides in DeferredHostBuilder.DeferredHost.StartAsync in the ASP.NET Core Mvc.Testing library. Start by opening that method, where _host.Services.GetRequiredService<IHostApplicationLifetime>() is called to register an ApplicationStarted callback. Wrap that service resolution in a try/catch for ObjectDisposedException: if caught, skip the callback registration and continue to await the start TaskCompletionSource, which will already be faulted with the app's real startup exception. Run the regression test from linked PR #69685 to confirm CreateClient() consistently throws the original startup error instead of the ObjectDisposedException.
Written by the indexing model from the issue text.
Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
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:
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.- Meanwhile, on the test thread,
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 (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:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOptions<MyOptions>().Validate(_ => false, "invalid").ValidateOnStart();
var app = builder.Build();
app.Run();
public class MyOptions { }
public partial class Program { }
// Test
public class StartupFailureTests
{
[Fact]
public void CreateClient_ThrowsStartupException()
{
using var factory = new SlowStartFactory();
Assert.Throws<OptionsValidationException>(() => factory.CreateClient());
// Actual: ObjectDisposedException
}
private sealed class SlowStartFactory : WebApplicationFactory<Program>
{
protected override IHost CreateHost(IHostBuilder builder)
{
var host = builder.Build();
Thread.Sleep(500); // let the app thread fail and dispose the host first
host.Start();
return host;
}
}
}
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.
DeferredHostBuilderis unchanged onmain. - Related: #58442 reports the same
ObjectDisposedExceptionfrom other call sites (HostingListener.CreateHost, and theIServerlookup inWebApplicationFactory.ConfigureHostBuilder). The fix proposed here only covers theDeferredHost.StartAsyncstack and doesn't address those. - Related: #38335. A July 2023 comment there reports this exact failure (
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 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.
- Dominant language
- C#
- Stars
- 38.5k
- Forks
- 13.8k
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 221
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/aspnetcore
-
area-middleware
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
dotnet/aspnetcore#69849 · 1 comment ·
Maintainers usually reply within 1 day
-
area-signalr
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
dotnet/aspnetcore#69842 · 1 comment ·
Maintainers usually reply within 1 day
-
area-minimal NativeAOT
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
dotnet/aspnetcore#69758 · 1 comment ·
Maintainers usually reply within 1 day
-
area-networking feature-httpsys
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
dotnet/aspnetcore#69671 · 1 comment ·
Maintainers usually reply within 1 day
-
.NET 10 OpenAPI emits an object constraint for floating-point valuesPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
dotnet/aspnetcore#69604 · 1 comment ·
Maintainers usually reply within 1 day
All issues in dotnet/aspnetcore
Similar issues
-
ASP.NET Core container quickstart: `docker-run: debug` task sample is missing its closing braceOpen
Difficulty 1/5 Under an hour Newbie friendliness 88/100
microsoft/vscode-docs#10499 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
python triage
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
microsoft/semantic-kernel#14615 ·
Maintainers usually reply within 3 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
vzeol/DeployCraft#125 ·
Maintainers usually reply within 1 day