Skip to content

TypeScript AppHost: addCSharpAppWithOptions callback causes JSON-RPC deadlock when calling properties on callback context #15152

@joperezr

Description

@joperezr

Description

When using addCSharpAppWithOptions in a TypeScript AppHost, the callback that receives ProjectResourceOptions causes a deadlock. The callback tries to set a property (e.g., opts.launchProfileName.set("https")), which makes a nested JSON-RPC call back to the server. The server appears to be blocked waiting for the callback to complete, creating a circular dependency.

Reproduction

// This deadlocks — the apphost hangs indefinitely
const admin = await builder.addCSharpAppWithOptions("boardadmin", "./MyProject", async (opts) => {
    await opts.launchProfileName.set("https"); // Nested RPC call deadlocks
});

Debug Evidence

Running with aspire run --debug shows:

  1. The TypeScript apphost sends addCSharpAppWithOptions to the server (>> invokeCapability)
  2. The server receives it and needs to invoke the callback
  3. Inside the callback, the TypeScript code calls opts.launchProfileName.set("https") which sends another RPC call to the server
  4. The response (<<) for addCSharpAppWithOptions never arrives — the server is blocked
  5. The CLI times out after 60 seconds: "Timed out waiting for AppHost server to start"

The last server log line shows:

>> invokeCapability(Aspire.Hosting/addCSharpAppWithOptions) args: {"builder":...}

No corresponding << response is ever logged.

Analysis

The issue is that the callback execution model doesn't support re-entrant RPC calls. When the server invokes the TypeScript callback, it blocks waiting for the callback result. But the callback needs to make another RPC call to the server (to set properties on ProjectResourceOptions), which requires the server to process a new incoming request — creating a deadlock.

Contrast with withConfiguration for YARP: That method has [AspireExport("withConfiguration", RunSyncOnBackgroundThread = true)], which runs the callback on a background thread and allows re-entrant RPC processing. addCSharpAppWithOptions (or the underlying addProjectWithOptions) may lack this attribute.

Workaround

Use addProject with an explicit launch profile name instead:

const admin = await builder.addProject("boardadmin", "./MyProject", "https");

However, this returns ProjectResource which lacks publishAsAzureContainerApp (only available on CSharpAppResource).

Impact

  • Users cannot use addCSharpAppWithOptions or addProjectWithOptions in TypeScript AppHosts
  • This blocks access to CSharpAppResource-specific methods like publishAsAzureContainerApp
  • Any API that uses callbacks with property setters on the callback context may be affected

Environment

  • Aspire CLI: 13.2.0-preview.1.26161.4
  • OS: Windows
  • Transport: Named pipes (JSON-RPC via vscode-jsonrpc)

Metadata

Metadata

Assignees

Labels

area-polyglotIssues related to polyglot apphostsneeds-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions