Support AppHost startup timeout env var - #16686
Conversation
Reuse the wait timeout option for start and run so slow AppHost builds or startups can wait longer than the default and show actionable timeout guidance. Cover start, run, detached startup, invalid timeout validation, and canceled process cleanup with CLI tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 16686Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 16686" |
Replace the custom advancing time provider with Microsoft.Extensions.Time.Testing.FakeTimeProvider configured with AutoAdvanceAmount. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR extends the Aspire CLI startup experience by adding configurable startup timeouts to aspire run and aspire start, aligning them with existing aspire wait --timeout semantics and improving timeout guidance. It also refactors detached launching to be testable and updates process-wait cancellation behavior to reduce the risk of orphaned processes.
Changes:
- Add
--timeoutsupport (and validation) toaspire runandaspire start, and propagate the configured timeout into detached AppHost launch waiting logic. - Introduce an
IDetachedProcessLauncherabstraction to make detached launching testable and injectable. - Improve timeout messaging (including localization resources) and add process cancellation coverage to ensure spawned processes are terminated when waits are canceled.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs | Registers the default detached process launcher in the test DI container. |
| tests/Aspire.Cli.Tests/TestServices/TestDetachedProcessLauncher.cs | Adds a test double for detached process launching plus a test TimeProvider. |
| tests/Aspire.Cli.Tests/DotNet/ProcessExecutionTests.cs | Adds a regression test ensuring cancellation kills the spawned process. |
| tests/Aspire.Cli.Tests/Commands/StartCommandTests.cs | Adds coverage for start --timeout parsing/validation and timeout behavior in detached launch. |
| tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs | Adds coverage for run --timeout parsing/validation and detached timeout behavior. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.zh-Hant.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.zh-Hans.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.tr.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.ru.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.pt-BR.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.pl.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.ko.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.ja.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.it.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.fr.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.es.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.de.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.cs.xlf | Updates localized resource entry for the new formatted timeout guidance string. |
| src/Aspire.Cli/Resources/RunCommandStrings.resx | Updates the timeout message to include the configured seconds and guidance to retry with higher --timeout. |
| src/Aspire.Cli/Program.cs | Registers IDetachedProcessLauncher in the CLI host DI container. |
| src/Aspire.Cli/Processes/IDetachedProcessLauncher.cs | Introduces the detached process launcher/process abstractions and a default implementation. |
| src/Aspire.Cli/DotNet/ProcessExecution.cs | Ensures cancellation of WaitForExitAsync kills the process tree. |
| src/Aspire.Cli/Commands/WaitCommand.cs | Centralizes timeout option creation and timeout validation for reuse. |
| src/Aspire.Cli/Commands/StartCommand.cs | Adds --timeout support to start and forwards it to detached launch waiting. |
| src/Aspire.Cli/Commands/RunCommand.cs | Adds --timeout support to run, applies it to build/backchannel waits, and threads it through detached launch. |
| src/Aspire.Cli/Commands/AppHostLauncher.cs | Adds optional --timeout to shared launch options and uses it for detached backchannel wait; uses injectable detached launcher. |
Comments suppressed due to low confidence (1)
src/Aspire.Cli/Commands/AppHostLauncher.cs:350
- On timeout, this kills only the detached child CLI process. Since the child may have already spawned the AppHost (grandchild), killing just the parent process can leave the AppHost running. Consider ensuring the kill operation terminates the entire process tree (consistent with other CLI process shutdown paths) so a timed-out
start/detachedrundoesn’t orphan an AppHost/build process.
if (!result.ChildProcess.HasExited)
{
try
{
result.ChildProcess.Kill();
Use a single timeout budget for run startup, observe pending AppHost runs during timeout cleanup, and make detached process termination explicit about process-tree cleanup. Keep cancellation propagation reliable when process-tree termination fails. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
This should be an env variable not an argument. |
Replace the run/start timeout options with ASPIRE_CLI_START_TIMEOUT_SECONDS while keeping aspire wait --timeout unchanged. Update timeout guidance and validation to point users at the environment variable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use ASPIRE_CLI_START_TIMEOUT to match existing Aspire timeout environment variable naming. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Changed to using an ENV as it makes more sense |
|
Code-review only — no fresh CI artifacts on the current head, so I read the diff. Walked the env-var path end to end:
Tests cover boundary cases (empty, malformed, zero, negative) cleanly. No bugs to flag. LGTM. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 PR Test ResultsTested locally on macOS arm64 against the PR-built CLI Scenarios
Observations
Overall: ✅ PR verified. The configurable startup timeout, validation, error messaging, and process-tree cleanup all behave as expected in both attached and detached modes. |
Mitch Denny (mitchdenny)
left a comment
There was a problem hiding this comment.
Approving — the configurable startup timeout works correctly in both attached and detached modes; validation, error messaging, and process-tree cleanup all behave as expected (see test report comment above for the full scenario matrix).
Leaving three small findings inline (none blocking):
- A wall-clock bound in a
FakeTimeProvider-driven test that risks CI flakiness. - Default-timeout coupling to
WaitCommand.DefaultTimeoutSeconds(semantically distinct timeout). - Linked-CTS lifetime mismatch with the fire-and-forget
pendingRunobserver in the >5s cleanup edge case.
| stopwatch.Stop(); | ||
|
|
||
| Assert.Equal(CliExitCodes.FailedToDotnetRunAppHost, exitCode); | ||
| Assert.True(stopwatch.Elapsed < TimeSpan.FromSeconds(1), $"Expected startup timeout to use the remaining budget, but the command took {stopwatch.Elapsed}."); |
There was a problem hiding this comment.
Wall-clock assertion stopwatch.Elapsed < TimeSpan.FromSeconds(1) re-introduces a real-time dependency in a test that otherwise uses FakeTimeProvider. Under CI contention (cold JIT, GC pause, host scheduling) the harness can take >1s just to spin up the host and tear down. The exit-code and error-message assertions already prove the budget was shared across both waits; consider dropping the wall-clock bound or significantly loosening it (e.g. 30s) to avoid contention-only flakiness.
| { | ||
| public static bool TryGetTimeoutSeconds(IConfiguration configuration, IInteractionService interactionService, out int timeoutSeconds) | ||
| { | ||
| timeoutSeconds = WaitCommand.DefaultTimeoutSeconds; |
There was a problem hiding this comment.
The default for AppHost startup is coupled to WaitCommand.DefaultTimeoutSeconds, but these are semantically distinct timeouts: WaitCommand.DefaultTimeoutSeconds is the default for aspire wait --timeout (waiting for a resource to reach a state), whereas this one controls how long aspire run / aspire start will wait for the AppHost to build + connect the backchannel. A future change to the wait command's default would silently shift AppHost startup behavior across the CLI. Consider a dedicated AppHostStartupTimeout.DefaultTimeoutSeconds so the two defaults can evolve independently.
| Task<int> pendingRun; | ||
| var startupTimeout = TimeSpan.FromSeconds(timeoutSeconds); | ||
| var startupStartTimestamp = _timeProvider.GetTimestamp(); | ||
| using var runCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); |
There was a problem hiding this comment.
In the timeout path, if CancelAppHostStartupAsync exceeds its 5s grace it kicks off ObserveAppHostRunFailureAsync(pendingRun) as fire-and-forget and ExecuteAsync returns. The using var here then disposes the linked CTS while pendingRun is still running on a background task holding runCancellationTokenSource.Token. Operations against a disposed CTS's token (e.g. Register) throw ObjectDisposedException, which the observer will swallow at debug level — but the lifetime mismatch is worth either fixing (defer disposal until the observer completes) or annotating with a comment. Low-impact, lower confidence than the other two.
|
We should not timeout if the build is taking a long time. We probably need better error handling here. |
Resolved conflicts in AppHostLauncher.cs and RunCommand.cs by integrating main's WaitForAppHostStartupAsync/RunStartupHappyPathAsync refactor with this PR's startup timeout enforcement, and tightened the outer OCE catch so post-startup OCEs with unrelated tokens are not silently treated as user cancellation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
❓ CLI E2E Tests unknown — 96 passed, 0 failed, 5 unknown (commit View all recordings
📹 Recordings uploaded automatically from CI run #26391854835 |
|
✅ No documentation update needed. docs_required → PR creation failed due to infrastructure issue. Triggered signals: Documentation changes were prepared for |
|
A few comments/questions:
|
|
We need to fix that build time is included. That’s a bug |
That's by design, that the timeout include the build time, even called out in the PR title.
I am confused, because since the build time is included in this value, then you can definitly increase the allowance, that's the goal of this feature |
Description
AppHost builds or startups can take longer than the CLI's default wait, causing
aspire startandaspire runto fail without a clear escape hatch for slow machines or CI environments. This adds theASPIRE_CLI_START_TIMEOUTenvironment variable foraspire startandaspire run, including detached launches, while keepingaspire wait --timeoutas the explicit wait-command option.The timeout guidance now tells users to set
ASPIRE_CLI_START_TIMEOUTto a higher value, and invalid values produce a clear error. This also keeps the process cleanup fixes from the earlier review feedback so a timed-outaspire rundoes not leave a build or AppHost process running after the CLI exits.Fixes # (issue)
N/A
Checklist
<remarks />and<code />elements on your triple slash comments?aspire.devissue: