Polyglot capability invocation gate: share it across integration-host connections, gate RunSyncOnBackgroundThread exports, and test async release

Open
#21,746 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 2 days

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
42/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
csharp
Domain
backend, testing

Research direction

Start with CapabilityDispatcher, RemoteHostServer, HandleRegistry, and the RunSyncOnBackgroundThread export flag; trace how the invocation gate is registered and used for both integration host connections and background thread exports. Then inspect the callback proxy tests and add coverage proving another gated invocation completes while an async export is awaiting a TaskCompletionSource. Done means shared builder mutations are gated and the async release behavior is covered without gating long-running exports such as run.

Written by the indexing model from the issue text.

Description

area-integrations

Follow-up from #21519 (Serialize synchronous capability invocations per polyglot guest connection). @sebastienros raised three non-blocking points in review. I checked each one against the PR head 17de5ac2, and all three need follow-up work.

1. The gate is per connection, but the builder is shared when integration hosts are enabled
  • CapabilityDispatcher creates its own gate (private readonly CapabilityInvocationGate _invocationGate = new();), and RemoteHostServer registers the dispatcher as scoped.
  • When integrationConfiguration.Enabled is set, RemoteHostServer registers HandleRegistry as a singleton. Integration hosts then call builder APIs such as builder.addContainer(...) over their own connections, which means they go through a different dispatcher and a different gate.
  • Effect: if a guest leaves addKafka(...) (forwarded to the host) and addPostgres(...) un-awaited, the two calls can still overlap on the shared List<IResource> and IServiceCollection.
  • Proposal: register CapabilityInvocationGate in DI with the same lifetime rule as HandleRegistry (singleton when integration hosts are enabled, scoped otherwise) and inject it into CapabilityDispatcher. External capabilities are forwarded before they reach InvokeMethodAsync, so a guest call doesn't hold the gate while the host re-enters.
2. Exports marked RunSyncOnBackgroundThread skip the gate
  • InvokeMethodAsync returns early with Task.Run(() => InvokeMethodCore(...)) when RunSyncOnBackgroundThread is set, so those calls never take the gate.
  • That flag isn't limited to run. AspireExportAnalyzer recommends it for any export that invokes a synchronous delegate inline, so builder-mutating exports use it too. For example, MySqlBuilderExtensions.WithPhpMyAdmin is [AspireExport(RunSyncOnBackgroundThread = true)] and calls Resources.OfType<...>().SingleOrDefault() and AddResource(...).
  • Effect: an un-awaited mysql.withPhpMyAdmin() alongside an un-awaited addContainer(...) can lose a List<IResource> add or throw "Collection was modified". This is a pre-existing issue on main, not a regression from #21519.
  • Proposal: now that callback proxies release the gate, run these exports under the gate (Task.Run(() => _invocationGate.RunAsync(() => InvokeMethodCore(...)))). Long-running exports such as run must stay ungated, so this needs a way to tell the two kinds apart, such as a separate attribute property or a dedicated opt-out.
3. No test asserts that the gate is released when an async export returns its Task
  • GateProbe is synchronous, and the re-entry tests release the gate through the callback proxy. These tests would still pass if a later change held the gate across UnwrapAsyncResultAsync. That change would hang the connection whenever an async export waits on another guest call.
  • Proposal: add a test with an async export that awaits a TaskCompletionSource, and assert that a second gated invocation completes before the source is set.

🤖 Posted by AspireAgent (AI Agent)

Dominant language
C#
Stars
6.4k
Forks
1k
Avg merge
3d 3h
Merged PRs (30d)
349

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from microsoft/aspire

All issues in microsoft/aspire

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.