Fix TypeScript AppHost async callback deadlock#17575
Conversation
Run the exported DistributedApplication.RunAsync capability on a background thread so startup callbacks invoked before the first await do not block StreamJsonRpc's non-concurrent synchronization context. Add an end-to-end TypeScript AppHost regression that recreates the lazy IOptions.Configure async callback pattern from #17487. 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 -- 17575Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17575" |
There was a problem hiding this comment.
Pull request overview
This PR fixes a TypeScript AppHost startup deadlock by ensuring the exported Aspire.Hosting/run capability runs its synchronous startup path off the JSON-RPC synchronization context.
Changes:
- Marks
DistributedApplication.RunAsyncwithRunSyncOnBackgroundThread = true. - Adds a TypeScript CLI E2E regression test that reproduces lazy
IOptions.Configureinvoking an async ATS callback duringBeforeStartEvent.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/Aspire.Hosting/DistributedApplication.cs |
Opts the run ATS export into background-thread invocation. |
tests/Aspire.Cli.EndToEnd.Tests/TypeScriptEmptyAppHostTemplateTests.cs |
Adds regression coverage for the TypeScript lazy-options async callback deadlock scenario. |
|
❌ CLI E2E Tests failed — 106 passed, 2 failed, 2 unknown (commit ❌ Failed Tests
View all recordings
📹 Recordings uploaded automatically from CI run #26552008600 |
PR Testing ReportPR Information
CLI Version Verification
Changes AnalyzedFiles Changed
Change Categories
Execution Environment Notes
Test Scenarios ExecutedScenario 1: Simple TypeScript AppHost start/stopObjective: Ensure the Steps:
Evidence:
Observations:
Scenario 2: TypeScript lazy-options async callback deadlock regressionObjective: Recreate the PR's deadlock shape: a TypeScript async callback stored in lazy Steps:
Evidence:
Observations:
Scenario 3: Callback validation failureObjective: Verify the same async callback/startup path fails safely when the callback produces invalid configuration. Steps:
Evidence:
Expected Unhappy-Path Outcome: Clear startup failure/non-zero exit without hanging. Observations:
Summary
Overall ResultPR VERIFIED No issues found in the tested TypeScript AppHost async callback startup scenarios. Artifacts
|
PR Testing ReportPR Information
CLI Version Verification
Changes AnalyzedFiles Changed
Change Categories
Test Scenarios ExecutedPR CLI install/version verificationObjective: Install dogfood CLI and verify it matches PR head commit. Steps:
Evidence:
Observations: TypeScript empty AppHost smoke testObjective: Verify a fresh TypeScript empty AppHost from the PR hive can be created, restored, built, started, and stopped. Steps:
Evidence:
Observations: Async callback deadlock regressionObjective: Verify lazy options configured through an async TypeScript callback do not deadlock during AppHost startup. Steps:
Evidence:
Observations: Async callback error propagationObjective: Verify an async TypeScript callback failure exits safely with a clear error rather than hanging. Steps:
Evidence:
Observations: Summary
Overall Result✅ PR VERIFIED Recommendations
|
|
✅ No documentation update needed. docs_required → already documented by name Triggered signal (1): The signal fired because the PR body mentions
The PR itself is a one-line bug fix ( |
|
/backport to release/13.4 |
|
Started backporting to |
Pulls in microsoft#17701 (Fix TypeScript deadlock repro E2E test) so PR GitHub Actions can complete. Main has been broken for PR events since microsoft#17575 added a test calling RunCommandFailFastAsync which microsoft#17588 renamed to RunCommandAsync. microsoft#17701 updates the call sites. No infra changes from this branch are affected by this merge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Description
Fixes #17487.
Follow-up to #17508.
TypeScript AppHosts could still deadlock during
await builder.build().run()when an async TypeScript callback was stored in lazyIOptions.Configureand invoked duringBeforeStartEvent. PR #17508 fixed the dispatcher behavior for exports that already opt intoRunSyncOnBackgroundThread, but the actualAspire.Hosting/runcapability was not opted in, so the real AppHost startup path could still run on StreamJsonRpc's non-concurrent synchronization context.This changes
DistributedApplication.RunAsyncto exportAspire.Hosting/runwithRunSyncOnBackgroundThread = true, keeping the synchronous startup portion ofRunAsyncoff the JSON-RPC synchronization context. Existing TypeScript AppHosts can keep using:The PR also adds a CLI E2E regression that recreates the exact failure shape:
RunSyncOnBackgroundThread = true.IOptions.Configure.BeforeStartEvent.aspire startsucceeds instead of timing out while waiting for the AppHost server.Validation:
.\restore.cmddotnet build tests\Aspire.Cli.EndToEnd.Tests\Aspire.Cli.EndToEnd.Tests.csproj /p:SkipNativeBuild=true.\localhive.ps1 -r win-x64 -Archive.aspire restore,npm run build,aspire start --format Json, andaspire stop.aspire startreturned successfully in ~15s instead of timing out after 60s.Checklist
<remarks />and<code />elements on your triple slash comments?