Is there an existing issue for this?
Describe the bug
DistributedApplicationBuilder.Build() can create a service provider and then
throw while resolving the host. When that happens, provider-owned disposable
services remain undisposed, while the caller receives no application or provider
through which cleanup can be performed.
This matters when Aspire is embedded in a long-lived process such as a test
runner or development tool. The build exception is catchable, and the containing
process can continue running with resources from the failed build still alive.
Actual behavior
DistributedApplicationBuilder.Build() delegates to the underlying generic host
builder. When that build throws after provider creation:
- no
DistributedApplication is returned;
- the partial provider is not disposed;
- the provider is not exposed to the caller;
- provider-owned resources remain alive until process termination.
Expected Behavior
If host resolution fails after the service provider has been created,
DistributedApplicationBuilder.Build() should:
- dispose the partial provider and its provider-owned services;
- preserve the original host-construction exception as the primary exception;
- leave no provider-owned resources active after
Build() returns by throwing.
Steps To Reproduce
Create a .NET 10 console application:
dotnet new console --framework net10.0
dotnet add package Aspire.Hosting --version 13.4.6
Replace Program.cs with:
using Aspire.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = DistributedApplication.CreateBuilder(
new DistributedApplicationOptions
{
Args = [],
AssemblyName = typeof(DisposalProbe).Assembly.GetName().Name,
ProjectDirectory = Directory.GetCurrentDirectory(),
DisableDashboard = true
});
DisposalProbe? probe = null;
builder.Services.AddSingleton<DisposalProbe>(
_ => probe = new DisposalProbe());
builder.Services.AddSingleton<IHostLifetime>(services =>
{
_ = services.GetRequiredService<DisposalProbe>();
throw new InvalidOperationException(
"Host construction failed after provider creation.");
});
try
{
using var application = builder.Build();
}
catch (InvalidOperationException exception)
{
Console.WriteLine(exception.Message);
}
Console.WriteLine(
$"Provider-owned singleton created: {probe is not null}");
Console.WriteLine(
$"Provider-owned singleton disposed: {probe?.IsDisposed}");
internal sealed class DisposalProbe : IDisposable
{
public bool IsDisposed { get; private set; }
public void Dispose() => IsDisposed = true;
}
Run it:
Output:
Host construction failed after provider creation.
Provider-owned singleton created: True
Provider-owned singleton disposed: False
The probe is created through the service-provider factory, rather than registered
as an externally owned singleton instance, so it should be disposed with its
owning provider.
Exceptions (if any)
The reproduction deliberately registers an IHostLifetime factory that throws:
System.InvalidOperationException: Host construction failed after provider creation.
The exception is caught by the reproduction, so the console output contains its
message rather than an unhandled-exception stack trace. No additional Aspire
exception is output. The reported problem is the provider-owned singleton state
that remains undisposed after the expected build exception is caught.
Aspire doctor output
Checking Aspire environment...
Aspire Environment Check
Aspire
✅ Aspire CLI version 13.4.6 (channel: stable)
.NET SDK
✅ .NET 10.0.102 installed (arm64)
Container Runtime
✅ Docker v29.5.3: running (auto-detected (default)) ← active
Environment
✅ HTTPS development certificate is trusted
Summary: 4 passed, 0 warnings, 0 failed
Aspire CLI Installations
Path:
/.dotnet/tools/.store/aspire.cli/13.4.6/
aspire.cli.osx-arm64/13.4.6/tools/net10.0/osx-arm64/aspire
Version: 13.4.6+87fe259e4fc244c599019a7b1304c85a1488f248
Channel: stable
Route: dotnet-tool
PATH status: not on PATH
Anything else?
Suggested direction
Please clarify the intended failed-build ownership contract.
A complete resolution would dispose the partial provider when host resolution
fails. The cleanup could be implemented in DistributedApplicationBuilder.Build()
or coordinated with the underlying generic-host implementation.
If Aspire intentionally cannot own that cleanup, a supported service-provider-
factory or host-builder customization seam would let an embedding caller assume
ownership. That is a fallback rather than the preferred behavior: callers should
not ordinarily need to replace the service-provider factory to make a failed
Build() release resources it created.
Reflection over the underlying host builder and global diagnostic interception
do not appear suitable as supported consumer workarounds.
Related work
Is there an existing issue for this?
Describe the bug
DistributedApplicationBuilder.Build()can create a service provider and thenthrow while resolving the host. When that happens, provider-owned disposable
services remain undisposed, while the caller receives no application or provider
through which cleanup can be performed.
This matters when Aspire is embedded in a long-lived process such as a test
runner or development tool. The build exception is catchable, and the containing
process can continue running with resources from the failed build still alive.
Actual behavior
DistributedApplicationBuilder.Build()delegates to the underlying generic hostbuilder. When that build throws after provider creation:
DistributedApplicationis returned;Expected Behavior
If host resolution fails after the service provider has been created,
DistributedApplicationBuilder.Build()should:Build()returns by throwing.Steps To Reproduce
Create a .NET 10 console application:
Replace
Program.cswith:Run it:
Output:
The probe is created through the service-provider factory, rather than registered
as an externally owned singleton instance, so it should be disposed with its
owning provider.
Exceptions (if any)
The reproduction deliberately registers an
IHostLifetimefactory that throws:The exception is caught by the reproduction, so the console output contains its
message rather than an unhandled-exception stack trace. No additional Aspire
exception is output. The reported problem is the provider-owned singleton state
that remains undisposed after the expected build exception is caught.
Aspire doctor output
Checking Aspire environment...
Aspire Environment Check
Aspire
✅ Aspire CLI version 13.4.6 (channel: stable)
.NET SDK
✅ .NET 10.0.102 installed (arm64)
Container Runtime
✅ Docker v29.5.3: running (auto-detected (default)) ← active
Environment
✅ HTTPS development certificate is trusted
Summary: 4 passed, 0 warnings, 0 failed
Aspire CLI Installations
Path:
/.dotnet/tools/.store/aspire.cli/13.4.6/
aspire.cli.osx-arm64/13.4.6/tools/net10.0/osx-arm64/aspire
Version: 13.4.6+87fe259e4fc244c599019a7b1304c85a1488f248
Channel: stable
Route: dotnet-tool
PATH status: not on PATH
Anything else?
Suggested direction
Please clarify the intended failed-build ownership contract.
A complete resolution would dispose the partial provider when host resolution
fails. The cleanup could be implemented in
DistributedApplicationBuilder.Build()or coordinated with the underlying generic-host implementation.
If Aspire intentionally cannot own that cleanup, a supported service-provider-
factory or host-builder customization seam would let an embedding caller assume
ownership. That is a fallback rather than the preferred behavior: callers should
not ordinarily need to replace the service-provider factory to make a failed
Build()release resources it created.Reflection over the underlying host builder and global diagnostic interception
do not appear suitable as supported consumer workarounds.
Related work
requests the customization seam described above. It does not cover disposal
when the default provider has already been created and host resolution fails.
addressed resources retained by builders that were never built. This report
covers a different failure boundary: the provider is created,
Build()throws,and the caller receives no host or provider to dispose.