-
Notifications
You must be signed in to change notification settings - Fork 854
aspire run hangs indefinitely when AppHost exits with code 0 (e.g. empty AppHost) #15685
Description
Description
When running aspire run against an AppHost that exits immediately with exit code 0 (e.g. an empty AppHost with no resources), the CLI hangs permanently displaying "Connecting to apphost..." and never terminates. From the user's perspective, aspire run appears frozen — the only way out is Ctrl+C.
Without --debug, this is what the user sees:
➜ emptyApphost aspire run
🗄 Updated settings file at 'aspire.config.json'.
🔐 Checking certificates...
🛠 Building apphost... apphost.cs
Connecting to apphost...
...and it stays there forever. The process never terminates.
With --debug, the log reveals the AppHost process exits immediately with code 0, but the CLI keeps retrying the backchannel socket connection indefinitely:
[17:16:21] [dbug] DotNetCliExecutionFactory: dotnet process with PID: 45966 has exited with code: 0
...
[17:16:21] [dbug] AppHostCliBackchannel: Connecting to AppHost backchannel at ...cli.sock... (autoReconnect=False)
[17:16:21] [dbug] AppHostCliBackchannel: Connecting to AppHost backchannel at ...cli.sock... (autoReconnect=False)
[17:16:22] [dbug] AppHostCliBackchannel: Connecting to AppHost backchannel at ...cli.sock... (autoReconnect=False)
... (continues indefinitely, every ~50ms then every 1s)
Root Cause
In DotNetCliRunner.StartBackchannelAsync, the SocketException catch that handles process exit only triggers when execution.ExitCode != 0:
catch (SocketException ex) when (execution is not null && execution.HasExited && execution.ExitCode != 0)
{
// Only handles non-zero exit codes
logger.LogDebug(ex, "AppHost process has exited. Unable to connect to backchannel at {SocketPath}", socketPath);
backchannelCompletionSource.SetException(backchannelException);
return;
}
catch (SocketException ex)
{
// Falls through to here for exit code 0 — retries forever
var waitingFor = DateTimeOffset.UtcNow - startTime;
if (waitingFor > TimeSpan.FromSeconds(10))
{
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
}
}When the AppHost exits with code 0, the first catch clause's when filter fails (ExitCode != 0 is false), so it falls through to the generic SocketException catch which retries indefinitely without ever checking execution.HasExited.
Suggested Fix
The when filter on line 172 should check execution.HasExited without restricting to non-zero exit codes. Additionally, the generic SocketException catch should also check if the process has exited to avoid retrying against a dead process:
catch (SocketException ex) when (execution is not null && execution.HasExited)
{
logger.LogDebug(ex, "AppHost process has exited (exit code {ExitCode}). Unable to connect to backchannel.", execution.ExitCode);
var backchannelException = new FailedToConnectBackchannelConnection("AppHost process has exited.", ex);
backchannelCompletionSource.SetException(backchannelException);
return;
}Reproduction Steps
- Create an empty AppHost file (e.g.
apphost.cs):var builder = DistributedApplication.CreateBuilder(args); builder.Build().Run();
- Run
aspire runin the directory - Observe the CLI displays "Connecting to apphost..." and hangs permanently
Expected Behavior
The CLI should detect the AppHost has exited (even with exit code 0), stop retrying the backchannel connection, and terminate gracefully.
Actual Behavior
The CLI displays "Connecting to apphost..." and hangs permanently, retrying backchannel socket connections indefinitely (~every 50ms initially, then every 1s after 10 seconds). Requires Ctrl+C to stop.
Environment
- .NET Aspire CLI 13.3.0-pr.15668
- macOS
- .NET 11.0 Preview 1