Skip to content

Add backchannel log replay for TUI support#14512

Merged
mitchdenny merged 1 commit intorelease/13.2from
feature/backchannel-tui-plumbing
Feb 25, 2026
Merged

Add backchannel log replay for TUI support#14512
mitchdenny merged 1 commit intorelease/13.2from
feature/backchannel-tui-plumbing

Conversation

@mitchdenny
Copy link
Copy Markdown
Member

@mitchdenny mitchdenny commented Feb 16, 2026

Summary

Adds server-side backchannel plumbing to support a rich TUI monitor experience in 13.3. These changes are purely additive — they add new RPC methods and enhance the log streaming infrastructure so that AppHosts built with 13.2 already expose the APIs the TUI will consume.

Changes

BackchannelLoggerProvider (enhanced)

  • Added circular replay buffer (1000 entries) so late-connecting clients see log history
  • Added pub-sub subscriber model: Subscribe() atomically returns a snapshot of buffered entries plus a per-subscriber channel for live entries
  • Multiple concurrent TUI clients can connect without entry loss or draining — each gets an independent snapshot and live stream
  • Dispose() uses TryComplete() to handle the DI forwarding double-disposal pattern

AppHostRpcTarget (new method)

  • Added GetAppHostLogEntriesAsync() — replays buffered entries then streams live entries via per-subscriber channel
  • Uses Subscribe()/Unsubscribe() lifecycle with proper cleanup in finally block
  • Linked cancellation token respects both client disconnect and AppHost shutdown

AuxiliaryBackchannelRpcTarget (new method)

  • Added GetAppHostLogEntriesAsync() delegate that forwards to AppHostRpcTarget
  • Enables auxiliary backchannel clients (CLI/TUI) to access log streaming

DistributedApplicationBuilder (DI registration)

  • Changed from AddSingleton<ILoggerProvider, BackchannelLoggerProvider>() to forwarding pattern
  • Enables AppHostRpcTarget to resolve BackchannelLoggerProvider directly for subscriber management

Tests (new file)

  • 4 new tests: replay buffer fill, eviction at 1000 capacity, snapshot isolation, concurrent subscriber fan-out

Risk Assessment

  • Low risk: All changes are additive — new methods, enhanced buffer, new subscriber model
  • Backward compatible: Existing backchannel contracts unchanged; new methods are simply unused until a TUI client connects
  • No breaking changes: The DI forwarding pattern preserves existing ILoggerProvider resolution

5 files changed, +193 / -57 lines

Copilot AI review requested due to automatic review settings February 16, 2026 07:20
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 16, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14512

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14512"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds server-side backchannel plumbing needed for the upcoming aspire monitor TUI by extending AppHost backchannel APIs with repository-root discovery and log history replay.

Changes:

  • Adds RepositoryRoot to GetAppHostInfoResponse and populates it via .git discovery.
  • Introduces a 1000-entry log replay buffer in BackchannelLoggerProvider and replays it before live streaming.
  • Exposes AppHost log streaming via the auxiliary backchannel and adjusts DI to allow resolving BackchannelLoggerProvider directly.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Aspire.Hosting/DistributedApplicationBuilder.cs Registers BackchannelLoggerProvider as a concrete singleton and forwards it to ILoggerProvider for shared instance resolution.
src/Aspire.Hosting/Backchannel/BackchannelLoggerProvider.cs Adds replay buffer and updates logger creation/disposal behavior.
src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs Extends AppHost info response contract with RepositoryRoot.
src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs Populates RepositoryRoot and adds auxiliary RPC method to stream AppHost log entries.
src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs Replays buffered log entries before reading from the live log channel.

Comment on lines +42 to +46
if (_replayBuffer.Count >= MaxReplayEntries)
{
_replayBuffer.RemoveAt(0);
}
_replayBuffer.Add(entry);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The replay buffer eviction uses List.RemoveAt(0), which is O(n) and will shift the remaining items on every log once the buffer reaches MaxReplayEntries. Under high log volume this can become a noticeable CPU hotspot. Consider switching to a ring buffer / circular array (or Queue<T> with a fixed-capacity policy) to make eviction O(1).

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +46
// Replay buffered entries first so late-connecting clients see history
var loggerProvider = serviceProvider.GetService<BackchannelLoggerProvider>();
if (loggerProvider is not null)
{
foreach (var entry in loggerProvider.GetReplaySnapshot())
{
yield return entry;
}
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetAppHostLogEntriesAsync reads from a single shared Channel<BackchannelLogEntry>. With the auxiliary backchannel supporting multiple concurrent connections, multiple callers can enumerate this method simultaneously; Channel semantics will then distribute entries across readers (each entry consumed by only one client), so clients will see partial/missing live logs. To support multiple clients, switch to a fan-out/broadcast approach (e.g., maintain per-subscriber channels/writers and write each log entry to all subscribers), or enforce a single active subscriber explicitly (reject additional subscribers).

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +46
// Replay buffered entries first so late-connecting clients see history
var loggerProvider = serviceProvider.GetService<BackchannelLoggerProvider>();
if (loggerProvider is not null)
{
foreach (var entry in loggerProvider.GetReplaySnapshot())
{
yield return entry;
}
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new log replay behavior isn’t covered by the existing backchannel test suite (there are tests for connection/state streaming, but none exercising GetAppHostLogEntriesAsync or verifying replay/broadcast semantics). Adding an integration test that logs a few messages, connects after the fact, and asserts the historical entries are received would help prevent regressions—especially once multiple auxiliary clients are supported.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 16, 2026

🎬 CLI E2E Test Recordings

The following terminal recordings are available for commit 1c73edd:

Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AgentInitCommand_WithMalformedMcpJson_ShowsErrorAndExitsNonZero ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
CreateAndDeployToDockerCompose ▶️ View Recording
CreateAndDeployToDockerComposeInteractive ▶️ View Recording
CreateAndPublishToKubernetes ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateEmptyAppHostProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateStartWaitAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
ResourcesCommandShowsRunningResources ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ❌ Upload failed
StopNonInteractiveMultipleAppHostsShowsError ❌ Upload failed
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording

📹 Recordings uploaded automatically from CI run #22339787086

@mitchdenny mitchdenny force-pushed the feature/backchannel-tui-plumbing branch from 7fdda57 to d51272a Compare February 16, 2026 07:58
@mitchdenny mitchdenny changed the title Add backchannel plumbing for TUI support Add backchannel log replay for TUI support Feb 16, 2026
@mitchdenny mitchdenny force-pushed the feature/backchannel-tui-plumbing branch 2 times, most recently from b00e8f7 to 45ed90f Compare February 16, 2026 08:16
@mitchdenny
Copy link
Copy Markdown
Member Author

/deployment-test

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Deployment tests starting on PR #14512...

This will deploy to real Azure infrastructure. Results will be posted here when complete.

View workflow run

@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing February 16, 2026 08:53 Inactive
@github-actions
Copy link
Copy Markdown
Contributor

Deployment E2E Tests passed

Summary: 19 passed, 0 failed, 0 cancelled

View workflow run

Passed Tests

  • ✅ AksStarterWithRedisDeploymentTests
  • ✅ AzureAppConfigDeploymentTests
  • ✅ AzureKeyVaultDeploymentTests
  • ✅ AppServicePythonDeploymentTests
  • ✅ PythonFastApiDeploymentTests
  • ✅ AuthenticationTests
  • ✅ AksStarterDeploymentTests
  • ✅ AzureLogAnalyticsDeploymentTests
  • ✅ AzureEventHubsDeploymentTests
  • ✅ AcaStarterDeploymentTests
  • ✅ VnetSqlServerInfraDeploymentTests
  • ✅ AzureContainerRegistryDeploymentTests
  • ✅ AppServiceReactDeploymentTests
  • ✅ AzureStorageDeploymentTests
  • ✅ VnetStorageBlobConnectivityDeploymentTests
  • ✅ VnetStorageBlobInfraDeploymentTests
  • ✅ VnetKeyVaultConnectivityDeploymentTests
  • ✅ AzureServiceBusDeploymentTests
  • ✅ VnetKeyVaultInfraDeploymentTests

🎬 Terminal Recordings

Test Recording
DeployAzureAppConfigResource ▶️ View Recording
DeployAzureContainerRegistryResource ▶️ View Recording
DeployAzureEventHubsResource ▶️ View Recording
DeployAzureKeyVaultResource ▶️ View Recording
DeployAzureLogAnalyticsResource ▶️ View Recording
DeployAzureServiceBusResource ▶️ View Recording
DeployAzureStorageResource ▶️ View Recording
DeployPythonFastApiTemplateToAzureAppService ▶️ View Recording
DeployPythonFastApiTemplateToAzureContainerApps ▶️ View Recording
DeployReactTemplateToAzureAppService ▶️ View Recording
DeployStarterTemplateToAks ▶️ View Recording
DeployStarterTemplateToAzureContainerApps ▶️ View Recording
DeployStarterTemplateWithKeyVaultPrivateEndpoint ▶️ View Recording
DeployStarterTemplateWithRedisToAks ▶️ View Recording
DeployStarterTemplateWithStorageBlobPrivateEndpoint ▶️ View Recording
DeployVnetKeyVaultInfrastructure ▶️ View Recording
DeployVnetSqlServerInfrastructure ▶️ View Recording
DeployVnetStorageBlobInfrastructure ▶️ View Recording

…riber support

Add server-side plumbing for TUI monitor support (13.3 preparation):

- BackchannelLoggerProvider: Add circular replay buffer (1000 entries) with
  pub-sub subscriber model. Subscribe() atomically returns a snapshot of
  buffered entries plus a per-subscriber channel for live entries, enabling
  multiple concurrent TUI clients without entry loss or draining.

- AppHostRpcTarget: Add GetAppHostLogEntriesAsync() that replays buffered
  entries then streams live entries via per-subscriber channel. Uses
  subscribe/unsubscribe lifecycle with proper cleanup in finally block.

- AuxiliaryBackchannelRpcTarget: Add GetAppHostLogEntriesAsync() delegate
  that forwards to AppHostRpcTarget for auxiliary backchannel clients.

- DistributedApplicationBuilder: Register BackchannelLoggerProvider as
  concrete singleton with ILoggerProvider forwarding, enabling direct
  resolution by AppHostRpcTarget.

- Tests: 4 new tests covering replay buffer fill, eviction at capacity,
  snapshot isolation, and concurrent subscriber fan-out.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny mitchdenny force-pushed the feature/backchannel-tui-plumbing branch from 45ed90f to 1c73edd Compare February 24, 2026 06:43
private readonly object _channelRegisteredLock = new();
private readonly CancellationTokenSource _backgroundChannelRegistrationCts = new();
private Task? _backgroundChannelRegistrationTask;
private readonly Queue<BackchannelLogEntry> _replayBuffer = new();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the circularbuffer?

Copy link
Copy Markdown
Contributor

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

@mitchdenny mitchdenny merged commit ab7b9ca into release/13.2 Feb 25, 2026
682 of 685 checks passed
@mitchdenny mitchdenny deleted the feature/backchannel-tui-plumbing branch February 25, 2026 01:03
@dotnet-policy-service dotnet-policy-service bot added this to the 13.2 milestone Feb 25, 2026
Copilot AI pushed a commit that referenced this pull request Mar 10, 2026
…riber support (#14512)

Add server-side plumbing for TUI monitor support (13.3 preparation):

- BackchannelLoggerProvider: Add circular replay buffer (1000 entries) with
  pub-sub subscriber model. Subscribe() atomically returns a snapshot of
  buffered entries plus a per-subscriber channel for live entries, enabling
  multiple concurrent TUI clients without entry loss or draining.

- AppHostRpcTarget: Add GetAppHostLogEntriesAsync() that replays buffered
  entries then streams live entries via per-subscriber channel. Uses
  subscribe/unsubscribe lifecycle with proper cleanup in finally block.

- AuxiliaryBackchannelRpcTarget: Add GetAppHostLogEntriesAsync() delegate
  that forwards to AppHostRpcTarget for auxiliary backchannel clients.

- DistributedApplicationBuilder: Register BackchannelLoggerProvider as
  concrete singleton with ILoggerProvider forwarding, enabling direct
  resolution by AppHostRpcTarget.

- Tests: 4 new tests covering replay buffer fill, eviction at capacity,
  snapshot isolation, and concurrent subscriber fan-out.

Co-authored-by: Mitch Denny <mitch@mitchdeny.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions bot locked and limited conversation to collaborators Mar 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Backchannel log replay plumbing for TUI support

3 participants