diff --git a/src/Aspire.Hosting/Dashboard/DashboardService.cs b/src/Aspire.Hosting/Dashboard/DashboardService.cs index e21e9d34c7..06074c0071 100644 --- a/src/Aspire.Hosting/Dashboard/DashboardService.cs +++ b/src/Aspire.Hosting/Dashboard/DashboardService.cs @@ -47,42 +47,54 @@ public override async Task WatchResources( IServerStreamWriter responseStream, ServerCallContext context) { - var (initialData, updates) = serviceData.SubscribeResources(); - - var data = new InitialResourceData(); - - foreach (var resource in initialData) + try { - data.Resources.Add(Resource.FromSnapshot(resource)); + await WatchResourcesInternal().ConfigureAwait(false); + } + catch (Exception ex) when (ex is OperationCanceledException or IOException && context.CancellationToken.IsCancellationRequested) + { + // Ignore cancellation and just return. Note that cancelled writes throw IOException. } - await responseStream.WriteAsync(new() { InitialData = data }).ConfigureAwait(false); - - await foreach (var batch in updates.WithCancellation(context.CancellationToken)) + async Task WatchResourcesInternal() { - WatchResourcesChanges changes = new(); + var (initialData, updates) = serviceData.SubscribeResources(); + + var data = new InitialResourceData(); - foreach (var update in batch) + foreach (var resource in initialData) { - var change = new WatchResourcesChange(); + data.Resources.Add(Resource.FromSnapshot(resource)); + } - if (update.ChangeType is ResourceSnapshotChangeType.Upsert) - { - change.Upsert = Resource.FromSnapshot(update.Resource); - } - else if (update.ChangeType is ResourceSnapshotChangeType.Delete) - { - change.Delete = new() { ResourceName = update.Resource.Name, ResourceType = update.Resource.ResourceType }; - } - else + await responseStream.WriteAsync(new() { InitialData = data }).ConfigureAwait(false); + + await foreach (var batch in updates.WithCancellation(context.CancellationToken)) + { + WatchResourcesChanges changes = new(); + + foreach (var update in batch) { - throw new FormatException($"Unexpected {nameof(ResourceSnapshotChange)} type: {update.ChangeType}"); + var change = new WatchResourcesChange(); + + if (update.ChangeType is ResourceSnapshotChangeType.Upsert) + { + change.Upsert = Resource.FromSnapshot(update.Resource); + } + else if (update.ChangeType is ResourceSnapshotChangeType.Delete) + { + change.Delete = new() { ResourceName = update.Resource.Name, ResourceType = update.Resource.ResourceType }; + } + else + { + throw new FormatException($"Unexpected {nameof(ResourceSnapshotChange)} type: {update.ChangeType}"); + } + + changes.Value.Add(change); } - changes.Value.Add(change); + await responseStream.WriteAsync(new() { Changes = changes }, context.CancellationToken).ConfigureAwait(false); } - - await responseStream.WriteAsync(new() { Changes = changes }, context.CancellationToken).ConfigureAwait(false); } } @@ -91,23 +103,35 @@ public override async Task WatchResourceConsoleLogs( IServerStreamWriter responseStream, ServerCallContext context) { - var subscription = serviceData.SubscribeConsoleLogs(request.ResourceName); - - if (subscription is null) + try + { + await WatchResourceConsoleLogsInternal().ConfigureAwait(false); + } + catch (Exception ex) when (ex is OperationCanceledException or IOException && context.CancellationToken.IsCancellationRequested) { - return; + // Ignore cancellation and just return. Note that cancelled writes throw IOException. } - await foreach (var group in subscription.WithCancellation(context.CancellationToken)) + async Task WatchResourceConsoleLogsInternal() { - WatchResourceConsoleLogsUpdate update = new(); + var subscription = serviceData.SubscribeConsoleLogs(request.ResourceName); - foreach (var (content, isErrorMessage) in group) + if (subscription is null) { - update.LogLines.Add(new ConsoleLogLine() { Text = content, IsStdErr = isErrorMessage }); + return; } - await responseStream.WriteAsync(update, context.CancellationToken).ConfigureAwait(false); + await foreach (var group in subscription.WithCancellation(context.CancellationToken)) + { + WatchResourceConsoleLogsUpdate update = new(); + + foreach (var (content, isErrorMessage) in group) + { + update.LogLines.Add(new ConsoleLogLine() { Text = content, IsStdErr = isErrorMessage }); + } + + await responseStream.WriteAsync(update, context.CancellationToken).ConfigureAwait(false); + } } } }