Skip to content

Commit

Permalink
Avoid cancellation messages in logs (dotnet#1590)
Browse files Browse the repository at this point in the history
* Avoid cancellation messages in logs

When clients unsubscribe from resources or console logs, suppress any logging due to cancellation exceptions.

This helps avoid log messages like this:

```
fail: Grpc.AspNetCore.Server.ServerCallHandler[6]
      Error when executing service method 'WatchResourceConsoleLogs'.
      System.OperationCanceledException: The operation was canceled.
         at System.Threading.Channels.AsyncOperation`1.GetResult(Int16 token)
         at Aspire.Hosting.Extensions.ChannelExtensions.GetBatches[T](Channel`1 channel, CancellationToken cancellationToken)+MoveNext() in D:\repos\aspire\src\Aspire.Hosting\Extensions\ChannelExtensions.cs:line 33
         at Aspire.Hosting.Extensions.ChannelExtensions.GetBatches[T](Channel`1 channel, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
         at Aspire.Hosting.Dashboard.FileLogSource.GetAsyncEnumerator(CancellationToken cancellationToken)+MoveNext() in D:\repos\aspire\src\Aspire.Hosting\Dashboard\FileLogSource.cs:line 26
         at Aspire.Hosting.Dashboard.FileLogSource.GetAsyncEnumerator(CancellationToken cancellationToken)+MoveNext() in D:\repos\aspire\src\Aspire.Hosting\Dashboard\FileLogSource.cs:line 26
         at Aspire.Hosting.Dashboard.FileLogSource.GetAsyncEnumerator(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
         at Aspire.Hosting.Dashboard.DashboardServiceData.<>c__DisplayClass6_0.<<SubscribeConsoleLogs>g__Enumerate|0>d.MoveNext() in D:\repos\aspire\src\Aspire.Hosting\Dashboard\DashboardServiceData.cs:line 54
      --- End of stack trace from previous location ---
         at Aspire.Hosting.Dashboard.DashboardServiceData.<>c__DisplayClass6_0.<<SubscribeConsoleLogs>g__Enumerate|0>d.MoveNext() in D:\repos\aspire\src\Aspire.Hosting\Dashboard\DashboardServiceData.cs:line 54
      --- End of stack trace from previous location ---
         at Aspire.Hosting.Dashboard.DashboardServiceData.<>c__DisplayClass6_0.<<SubscribeConsoleLogs>g__Enumerate|0>d.System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult(Int16 token)
         at Aspire.Hosting.Dashboard.DashboardService.WatchResourceConsoleLogs(WatchResourceConsoleLogsRequest request, IServerStreamWriter`1 responseStream, ServerCallContext context) in D:\repos\aspire\src\Aspire.Hosting\Dashboard\DashboardService.cs:line 101
         at Aspire.Hosting.Dashboard.DashboardService.WatchResourceConsoleLogs(WatchResourceConsoleLogsRequest request, IServerStreamWriter`1 responseStream, ServerCallContext context) in D:\repos\aspire\src\Aspire.Hosting\Dashboard\DashboardService.cs:line 101
         at Grpc.Shared.Server.ServerStreamingServerMethodInvoker`3.Invoke(HttpContext httpContext, ServerCallContext serverCallContext, TRequest request, IServerStreamWriter`1 streamWriter)
         at Grpc.Shared.Server.ServerStreamingServerMethodInvoker`3.Invoke(HttpContext httpContext, ServerCallContext serverCallContext, TRequest request, IServerStreamWriter`1 streamWriter)
         at Grpc.AspNetCore.Server.Internal.CallHandlers.ServerStreamingServerCallHandler`3.HandleCallAsyncCore(HttpContext httpContext, HttpContextServerCallContext serverCallContext)
         at Grpc.AspNetCore.Server.Internal.CallHandlers.ServerCallHandlerBase`3.<HandleCallAsync>g__AwaitHandleCall|8_0(HttpContextServerCallContext serverCallContext, Method`2 method, Task handleCall)
```

* Catch IOException too, and check cancellation token
  • Loading branch information
drewnoakes authored Jan 9, 2024
1 parent 6da17f9 commit 8aa6c18
Showing 1 changed file with 58 additions and 34 deletions.
92 changes: 58 additions & 34 deletions src/Aspire.Hosting/Dashboard/DashboardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,42 +47,54 @@ public override async Task WatchResources(
IServerStreamWriter<WatchResourcesUpdate> 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);
}
}

Expand All @@ -91,23 +103,35 @@ public override async Task WatchResourceConsoleLogs(
IServerStreamWriter<WatchResourceConsoleLogsUpdate> 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);
}
}
}
}

0 comments on commit 8aa6c18

Please sign in to comment.