Skip to content

Commit 2247a63

Browse files
committed
Fix swallowed watcher exceptions, deduplicate IsHiddenResource, reduce per-line allocations
- ResourceSnapshotWatcher: store post-initial-load exceptions in WatchException instead of silently dropping them - Remove duplicate IsHiddenResource from AppHostAuxiliaryBackchannel and TestAppHostAuxiliaryBackchannel; use ResourceSnapshotMapper.IsHiddenResource - LogsCommand.CollectLogsAsync: snapshot resource list once for non-follow path instead of re-enumerating ConcurrentDictionary per log line
1 parent 6b1353b commit 2247a63

4 files changed

Lines changed: 18 additions & 16 deletions

File tree

src/Aspire.Cli/Backchannel/AppHostAuxiliaryBackchannel.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ public async Task<List<ResourceSnapshot>> GetResourceSnapshotsAsync(bool include
271271

272272
if (!includeHidden)
273273
{
274-
snapshots = snapshots.Where(s => !IsHiddenResource(s)).ToList();
274+
snapshots = snapshots.Where(s => !ResourceSnapshotMapper.IsHiddenResource(s)).ToList();
275275
}
276276

277277
// Sort resources by name for consistent ordering.
@@ -312,7 +312,7 @@ public async IAsyncEnumerable<ResourceSnapshot> WatchResourceSnapshotsAsync(bool
312312

313313
await foreach (var snapshot in snapshots.WithCancellation(cancellationToken).ConfigureAwait(false))
314314
{
315-
if (!includeHidden && IsHiddenResource(snapshot))
315+
if (!includeHidden && ResourceSnapshotMapper.IsHiddenResource(snapshot))
316316
{
317317
continue;
318318
}
@@ -321,11 +321,6 @@ public async IAsyncEnumerable<ResourceSnapshot> WatchResourceSnapshotsAsync(bool
321321
}
322322
}
323323

324-
private static bool IsHiddenResource(ResourceSnapshot snapshot)
325-
{
326-
return snapshot.IsHidden || string.Equals(snapshot.State, "Hidden", StringComparison.OrdinalIgnoreCase);
327-
}
328-
329324
/// <inheritdoc />
330325
public async IAsyncEnumerable<ResourceLogLine> GetResourceLogsAsync(
331326
string? resourceName = null,

src/Aspire.Cli/Backchannel/ResourceSnapshotWatcher.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ internal sealed class ResourceSnapshotWatcher : IDisposable
1616
private readonly CancellationTokenSource _cts = new();
1717
private readonly TaskCompletionSource _initialLoadTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
1818
private readonly Task _watchTask;
19+
private volatile Exception? _watchException;
1920

2021
public ResourceSnapshotWatcher(IAppHostAuxiliaryBackchannel connection, bool includeHidden = false)
2122
{
@@ -61,10 +62,19 @@ private async Task WatchAsync(CancellationToken cancellationToken)
6162
}
6263
catch (Exception ex)
6364
{
64-
_initialLoadTcs.TrySetException(ex);
65+
if (!_initialLoadTcs.TrySetException(ex))
66+
{
67+
// Initial load already completed; store for callers to detect.
68+
_watchException = ex;
69+
}
6570
}
6671
}
6772

73+
/// <summary>
74+
/// Gets the exception that terminated the watch loop after the initial load, or <see langword="null"/> if the watch is still running.
75+
/// </summary>
76+
public Exception? WatchException => _watchException;
77+
6878
private void EnsureInitialLoadComplete()
6979
{
7080
if (!_initialLoadTcs.Task.IsCompletedSuccessfully)

src/Aspire.Cli/Commands/LogsCommand.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ private static async Task<IList<LogEntry>> CollectLogsAsync(
316316
{
317317
var logParser = new LogParser(ConsoleColor.Black);
318318
var logEntries = new LogEntries(int.MaxValue) { BaseLineNumber = 1 };
319+
// Snapshot the resource list once for the non-follow path since it doesn't change.
320+
var allSnapshots = resourceWatcher.GetAllResources().ToList();
319321
await foreach (var logLine in connection.GetResourceLogsAsync(resourceName, follow: false, cancellationToken).ConfigureAwait(false))
320322
{
321323
// When streaming all resources, skip logs from hidden resources
@@ -328,7 +330,7 @@ private static async Task<IList<LogEntry>> CollectLogsAsync(
328330
}
329331
}
330332

331-
logEntries.InsertSorted(ParseLogLine(logLine, logParser, resourceWatcher.GetAllResources()));
333+
logEntries.InsertSorted(ParseLogLine(logLine, logParser, allSnapshots));
332334
}
333335
return logEntries.GetEntries();
334336
}

tests/Aspire.Cli.Tests/TestServices/TestAppHostAuxiliaryBackchannel.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public Task<List<ResourceSnapshot>> GetResourceSnapshotsAsync(bool includeHidden
7171

7272
var snapshots = includeHidden
7373
? ResourceSnapshots
74-
: ResourceSnapshots.Where(s => !IsHiddenResource(s)).ToList();
74+
: ResourceSnapshots.Where(s => !ResourceSnapshotMapper.IsHiddenResource(s)).ToList();
7575
return Task.FromResult(snapshots);
7676
}
7777

@@ -88,7 +88,7 @@ public async IAsyncEnumerable<ResourceSnapshot> WatchResourceSnapshotsAsync(bool
8888

8989
foreach (var snapshot in ResourceSnapshots)
9090
{
91-
if (!includeHidden && IsHiddenResource(snapshot))
91+
if (!includeHidden && ResourceSnapshotMapper.IsHiddenResource(snapshot))
9292
{
9393
continue;
9494
}
@@ -98,11 +98,6 @@ public async IAsyncEnumerable<ResourceSnapshot> WatchResourceSnapshotsAsync(bool
9898
await Task.CompletedTask;
9999
}
100100

101-
private static bool IsHiddenResource(ResourceSnapshot snapshot)
102-
{
103-
return snapshot.IsHidden || string.Equals(snapshot.State, "Hidden", StringComparison.OrdinalIgnoreCase);
104-
}
105-
106101
public async IAsyncEnumerable<ResourceLogLine> GetResourceLogsAsync(
107102
string? resourceName = null,
108103
bool follow = false,

0 commit comments

Comments
 (0)