Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 38 additions & 28 deletions src/Aspire.Hosting/Dashboard/DashboardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Grpc.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Aspire.Hosting.Dashboard;

Expand All @@ -17,7 +18,7 @@ namespace Aspire.Hosting.Dashboard;
/// required beyond a single request. Longer-scoped data is stored in <see cref="DashboardServiceData"/>.
/// </remarks>
[Authorize(Policy = ResourceServiceApiKeyAuthorization.PolicyName)]
internal sealed partial class DashboardService(DashboardServiceData serviceData, IHostEnvironment hostEnvironment, IHostApplicationLifetime hostApplicationLifetime)
internal sealed partial class DashboardService(DashboardServiceData serviceData, IHostEnvironment hostEnvironment, IHostApplicationLifetime hostApplicationLifetime, ILogger<DashboardService> logger)
: Aspire.ResourceService.Proto.V1.DashboardService.DashboardServiceBase
{
// Calls that consume or produce streams must create a linked cancellation token
Expand Down Expand Up @@ -53,18 +54,11 @@ public override async Task WatchResources(
IServerStreamWriter<WatchResourcesUpdate> responseStream,
ServerCallContext context)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping, context.CancellationToken);
await ExecuteAsync(
WatchResourcesInternal,
context).ConfigureAwait(false);

try
{
await WatchResourcesInternal().ConfigureAwait(false);
}
catch (Exception ex) when (ex is OperationCanceledException or IOException && cts.Token.IsCancellationRequested)
{
// Ignore cancellation and just return. Note that cancelled writes throw IOException.
}

async Task WatchResourcesInternal()
async Task WatchResourcesInternal(CancellationToken cancellationToken)
{
var (initialData, updates) = serviceData.SubscribeResources();

Expand All @@ -75,9 +69,9 @@ async Task WatchResourcesInternal()
data.Resources.Add(Resource.FromSnapshot(resource));
}

await responseStream.WriteAsync(new() { InitialData = data }).ConfigureAwait(false);
await responseStream.WriteAsync(new() { InitialData = data }, cancellationToken).ConfigureAwait(false);

await foreach (var batch in updates.WithCancellation(cts.Token).ConfigureAwait(false))
await foreach (var batch in updates.WithCancellation(cancellationToken).ConfigureAwait(false))
{
WatchResourcesChanges changes = new();

Expand All @@ -101,7 +95,7 @@ async Task WatchResourcesInternal()
changes.Value.Add(change);
}

await responseStream.WriteAsync(new() { Changes = changes }, cts.Token).ConfigureAwait(false);
await responseStream.WriteAsync(new() { Changes = changes }, cancellationToken).ConfigureAwait(false);
}
}
}
Expand All @@ -111,18 +105,11 @@ public override async Task WatchResourceConsoleLogs(
IServerStreamWriter<WatchResourceConsoleLogsUpdate> responseStream,
ServerCallContext context)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping, context.CancellationToken);
await ExecuteAsync(
WatchResourceConsoleLogsInternal,
context).ConfigureAwait(false);

try
{
await WatchResourceConsoleLogsInternal().ConfigureAwait(false);
}
catch (Exception ex) when (ex is OperationCanceledException or IOException && cts.Token.IsCancellationRequested)
{
// Ignore cancellation and just return. Note that cancelled writes throw IOException.
}

async Task WatchResourceConsoleLogsInternal()
async Task WatchResourceConsoleLogsInternal(CancellationToken cancellationToken)
{
var subscription = serviceData.SubscribeConsoleLogs(request.ResourceName);

Expand All @@ -131,7 +118,7 @@ async Task WatchResourceConsoleLogsInternal()
return;
}

await foreach (var group in subscription.WithCancellation(cts.Token).ConfigureAwait(false))
await foreach (var group in subscription.WithCancellation(cancellationToken).ConfigureAwait(false))
{
var update = new WatchResourceConsoleLogsUpdate();

Expand All @@ -140,8 +127,31 @@ async Task WatchResourceConsoleLogsInternal()
update.LogLines.Add(new ConsoleLogLine() { LineNumber = lineNumber, Text = content, IsStdErr = isErrorMessage });
}

await responseStream.WriteAsync(update, cts.Token).ConfigureAwait(false);
await responseStream.WriteAsync(update, cancellationToken).ConfigureAwait(false);
}
}
}

private async Task ExecuteAsync(Func<CancellationToken, Task> execute, ServerCallContext serverCallContext)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping, serverCallContext.CancellationToken);

try
{
await execute(cts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException) when (cts.Token.IsCancellationRequested)
{
// Ignore cancellation and just return.
}
catch (IOException) when (cts.Token.IsCancellationRequested)
{
// Ignore cancellation and just return. Cancelled writes throw IOException.
}
catch (Exception ex)
{
logger.LogError(ex, $"Error executing service method '{serverCallContext.Method}'.");
throw;
}
}
}
1 change: 1 addition & 0 deletions src/Aspire.Hosting/DistributedApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)
_innerBuilder.Logging.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.Warning);
_innerBuilder.Logging.AddFilter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Error);
_innerBuilder.Logging.AddFilter("Aspire.Hosting.Dashboard", LogLevel.Error);
_innerBuilder.Logging.AddFilter("Grpc.AspNetCore.Server.ServerCallHandler", LogLevel.Error);

// This is so that we can see certificate errors in the resource server in the console logs.
// See: https://github.com/dotnet/aspire/issues/2914
Expand Down