Add backchannel log replay for TUI support#14512
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14512Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14512" |
There was a problem hiding this comment.
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
RepositoryRoottoGetAppHostInfoResponseand populates it via.gitdiscovery. - Introduces a 1000-entry log replay buffer in
BackchannelLoggerProviderand replays it before live streaming. - Exposes AppHost log streaming via the auxiliary backchannel and adjusts DI to allow resolving
BackchannelLoggerProviderdirectly.
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. |
| if (_replayBuffer.Count >= MaxReplayEntries) | ||
| { | ||
| _replayBuffer.RemoveAt(0); | ||
| } | ||
| _replayBuffer.Add(entry); |
There was a problem hiding this comment.
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).
| // 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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).
| // 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
🎬 CLI E2E Test RecordingsThe following terminal recordings are available for commit
📹 Recordings uploaded automatically from CI run #22339787086 |
7fdda57 to
d51272a
Compare
b00e8f7 to
45ed90f
Compare
|
/deployment-test |
|
🚀 Deployment tests starting on PR #14512... This will deploy to real Azure infrastructure. Results will be posted here when complete. |
|
✅ Deployment E2E Tests passed Summary: 19 passed, 0 failed, 0 cancelled Passed Tests
🎬 Terminal Recordings
|
…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>
45ed90f to
1c73edd
Compare
| private readonly object _channelRegisteredLock = new(); | ||
| private readonly CancellationTokenSource _backgroundChannelRegistrationCts = new(); | ||
| private Task? _backgroundChannelRegistrationTask; | ||
| private readonly Queue<BackchannelLogEntry> _replayBuffer = new(); |
There was a problem hiding this comment.
Why not use the circularbuffer?
…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>
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)
Subscribe()atomically returns a snapshot of buffered entries plus a per-subscriber channel for live entriesDispose()usesTryComplete()to handle the DI forwarding double-disposal patternAppHostRpcTarget (new method)
GetAppHostLogEntriesAsync()— replays buffered entries then streams live entries via per-subscriber channelSubscribe()/Unsubscribe()lifecycle with proper cleanup infinallyblockAuxiliaryBackchannelRpcTarget (new method)
GetAppHostLogEntriesAsync()delegate that forwards toAppHostRpcTargetDistributedApplicationBuilder (DI registration)
AddSingleton<ILoggerProvider, BackchannelLoggerProvider>()to forwarding patternAppHostRpcTargetto resolveBackchannelLoggerProviderdirectly for subscriber managementTests (new file)
Risk Assessment
ILoggerProviderresolution5 files changed, +193 / -57 lines