Skip to content

CLI Ctrl+C shutdown can take too long during AppHost startup #17569

@davidfowl

Description

@davidfowl

Summary

Pressing Ctrl+C while the Aspire CLI is starting an AppHost can take too long to end the CLI process. This is especially noticeable when startup is stuck waiting for AppHost/backchannel readiness.

Repro context

While investigating a TypeScript AppHost startup issue:

cd D:\dev\git\dogfood\helllo
aspire start --debug

The CLI got stuck during startup waiting for the child AppHost/backchannel path. Pressing Ctrl+C does not feel immediate; shutdown can take several seconds before the CLI process exits.

What I expected

Ctrl+C should request cancellation immediately and the CLI should exit promptly, or at least provide visible feedback that it is waiting on child process shutdown and then force completion quickly.

What happens

The CLI can sit for multiple seconds during cancellation. The current cancellation flow has a few places that appear to add delay:

  • Program.Main creates ConsoleCancellationManager(processTerminationTimeout: TimeSpan.FromSeconds(5)).
  • ConsoleCancellationManager.Cancel(...) calls _cts.Cancel() and then synchronously waits for the command handler:
if (startedHandler is null || !startedHandler.Wait(_processTerminationTimeout))
{
    _processTerminationCompletionSource.TrySetResult(forcedTerminationExitCode);
}
  • Program.Main is waiting on:
Task.WhenAny(handlerTask, cancellationManager.ProcessTerminationCompletionSource.Task)

but ProcessTerminationCompletionSource is only signaled after the synchronous wait in the signal handler finishes.

  • Separately, AppHost startup timeout/cancellation uses another 5-second wait in RunCommand.CancelAppHostStartupAsync:
await pendingRun.WaitAsync(s_appHostStartupCancellationTimeout, _timeProvider)

So a stuck startup path can accumulate delays before the CLI returns.

Suggested investigation

Consider changing the cancellation manager so the Ctrl+C handler does not synchronously block for the full termination timeout before Program.Main can observe cancellation. For example:

  • Signal cancellation immediately and schedule the forced-completion timeout asynchronously.
  • Emit a debug/info log when Ctrl+C is received and when forced completion is triggered.
  • Make a second Ctrl+C force termination immediately.
  • Audit RunCommand.CancelAppHostStartupAsync and child process teardown so startup cancellation does not add another long wait without user-visible progress.

Related observations

In the TypeScript AppHost repro, the profile showed the long wait in:

  • aspire/cli/start_apphost.wait_for_backchannel
  • aspire/cli/run_apphost.wait_for_backchannel

The remotehost server itself was still alive/listening, and the actual underlying startup failure in a managed repro was missing npm. This issue is specifically about the Ctrl+C/shutdown responsiveness when the CLI is stuck in one of these startup waits.

Metadata

Metadata

Assignees

Labels

Type

No fields configured for Bug.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions