-
Notifications
You must be signed in to change notification settings - Fork 10.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Host stopped and disposed twice when WebApplicationFactory<> is used with the minimal api #40271
Comments
This sounds a bit like #37631. |
Hi @martincostello, It would be strange having to implement |
Thanks for contacting us. |
Confirms, the problem exists. I am facing with the problem in the same scenario.
|
I'm experiencing the same issue. |
Thanks for contacting us. |
+1 any progress or a workaround for this? |
Does anyone have a minimal repro of this - the repro linked in the opening post doesn't appear to be there anymore. |
Hey @mitchdenny, I just created one, https://github.com/salarzobir/DisposingHostTwiceIssue |
This also happens without using minimal API, while using an old school Startup class. |
Same here. Dotnet 7, Xunit, WebApplicationFactory |
Same problem still exists in .NET 8 |
Same problem in .NET 8 with Xunit, WebApplicationFactory. I had to do workaround through modifying |
Same problem |
I changed the labels because this issue has seen a bit activity over the years, and I just got pinged about it. I think the proper fix belongs in dotnet/runtime's |
Is this already fixed in .NET 9? |
The workaround is to override Here's a rudimentary implementation of this workaround: using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Sample.Test;
public sealed class WebApplicationFactoryWrapper<TEntryPoint> : WebApplicationFactory<TEntryPoint> where TEntryPoint : class
{
private readonly static TimeSpan ShutdownTimeout = TimeSpan.FromSeconds(5);
public override async ValueTask DisposeAsync()
{
await StopApplication().ConfigureAwait(false);
await WaitForDisposal().ConfigureAwait(false);
foreach (var factory in Factories)
{
await factory.DisposeAsync().ConfigureAwait(false);
}
}
private async Task StopApplication(CancellationToken forcefulStoppingToken = default)
{
try
{
var tcs = new TaskCompletionSource();
var lifetime = Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStopped.Register(() => tcs.TrySetResult());
lifetime.StopApplication();
await tcs.Task.WaitAsync(ShutdownTimeout, forcefulStoppingToken).ConfigureAwait(false);
}
catch (Exception)
{
}
}
private async Task WaitForDisposal(CancellationToken cancellationToken = default)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
// IHostApplicationLifetime.ApplicationStopped is triggered before the host (and its service collection)
// is disposed, so additionally wait until the service collection is disposed for a clean shutdown.
_ = Services.GetRequiredService<IHostApplicationLifetime>();
await Task.Delay(1, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception)
{
}
}
} It's hacky, but it works and suffices for testing. The proper fix I think would be to use Feel free to check out https://github.com/nickogl/AspNetCore.IntegrationTesting which includes this workaround and offers some neat functionality on top of |
Is there an existing issue for this?
Describe the bug
Description
When using
WebApplicationFactory<>
together with the minimal apiWebApplication
, the underlyingHost
is stopped and disposed twice from separate threads.This can lead to integration tests failing e.g. when there are custom
IHostedService
s registered. Dpending on the race condition, a particular instance ofIHostedService
can be in order: stopped, then disposed, then attempted to stop again from a different thread; or attempted to stop twice from different threads at the same time.What happens under the hood
With minimal api and the
WebApplicationFactory<Program>
, the wholeProgram
content is executed. It goes up toMicrosoft.Extensions.Hosting.HostingAbstractionsHostExtensions.WaitForShutdownAsync()
and waits for theIHostApplicationLifetime.ApplicationStopping
event.The test runs and at the end the WebApplicationFactory is being disposed. It calls (with some wrapper classes skipped here for brevity)
Microsoft.Extensions.Hosting.Internal.Host.StopAsync()
The
Host.StopAsync()
callsApplicationLifetime.StopApplication()
, and thus raises the beforementionedIHostApplicationLifetime.ApplicationStopping
event, thus unblocking the "main" thread executingHostingAbstractionsHostExtensions.WaitForShutdownAsync()
, which then... callsHost.StopAsync()
again.Expected Behavior
Host
and any registeredIHostedService
s should be stopped and disposed once.Steps To Reproduce
https://github.com/mateusz-duchnowski-trainline/disposing-host-twice-issue
run
dotnet test
example output, where
A
is anIHostedService
:Exceptions (if any)
No response
.NET Version
6.0.200-preview.22055.15
Anything else?
No response
The text was updated successfully, but these errors were encountered: