diff --git a/src/Aspire.Dashboard/Model/IDashboardViewModelService.cs b/src/Aspire.Dashboard/Model/IDashboardViewModelService.cs index 59bd17ee74..60e31de2d7 100644 --- a/src/Aspire.Dashboard/Model/IDashboardViewModelService.cs +++ b/src/Aspire.Dashboard/Model/IDashboardViewModelService.cs @@ -7,8 +7,9 @@ public interface IDashboardViewModelService { string ApplicationName { get; } - ViewModelMonitor GetResources(); + ViewModelMonitor GetResources(); } -public record ViewModelMonitor(List Snapshot, IAsyncEnumerable> Watch) - where TViewModel : ResourceViewModel; +public record ViewModelMonitor( + List Snapshot, + IAsyncEnumerable Watch); diff --git a/src/Aspire.Dashboard/Model/ResourceChanged.cs b/src/Aspire.Dashboard/Model/ResourceChange.cs similarity index 61% rename from src/Aspire.Dashboard/Model/ResourceChanged.cs rename to src/Aspire.Dashboard/Model/ResourceChange.cs index ccbe968b6e..afbd140fc7 100644 --- a/src/Aspire.Dashboard/Model/ResourceChanged.cs +++ b/src/Aspire.Dashboard/Model/ResourceChange.cs @@ -3,5 +3,4 @@ namespace Aspire.Dashboard.Model; -public sealed record ResourceChanged(ObjectChangeType ObjectChangeType, T Resource) - where T : class; +public sealed record ResourceChange(ObjectChangeType ObjectChangeType, ResourceViewModel Resource); diff --git a/src/Aspire.Dashboard/Resources/ControlsStrings.Designer.cs b/src/Aspire.Dashboard/Resources/ControlsStrings.Designer.cs index ba10f08212..e81a6ee553 100644 --- a/src/Aspire.Dashboard/Resources/ControlsStrings.Designer.cs +++ b/src/Aspire.Dashboard/Resources/ControlsStrings.Designer.cs @@ -1,6 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/src/Aspire.Hosting/Dashboard/DashboardViewModelService.cs b/src/Aspire.Hosting/Dashboard/DashboardViewModelService.cs index 420b50ad1b..cada70b84c 100644 --- a/src/Aspire.Hosting/Dashboard/DashboardViewModelService.cs +++ b/src/Aspire.Hosting/Dashboard/DashboardViewModelService.cs @@ -31,7 +31,10 @@ internal sealed partial class DashboardViewModelService : IDashboardViewModelSer private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly CancellationToken _cancellationToken; + // Private channels, for decoupling producer/consumer and serialising updates. private readonly Channel<(WatchEventType, string, CustomResource?)> _kubernetesChangesChannel; + private readonly Channel _resourceViewModelChangesChannel; + private readonly Dictionary _containersMap = []; private readonly Dictionary _executablesMap = []; private readonly Dictionary _servicesMap = []; @@ -40,9 +43,7 @@ internal sealed partial class DashboardViewModelService : IDashboardViewModelSer private readonly ConcurrentDictionary> _additionalEnvVarsMap = []; private readonly HashSet _containersWithTaskStarted = []; - private readonly Channel> _resourceViewModelChangesChannel; - - private readonly ViewModelProcessor _resourceViewModelProcessor; + private readonly ViewModelProcessor _resourceViewModelProcessor; public DashboardViewModelService( DistributedApplicationModel applicationModel, KubernetesService kubernetesService, IHostEnvironment hostEnvironment, ILoggerFactory loggerFactory) @@ -52,23 +53,23 @@ public DashboardViewModelService( _applicationName = ComputeApplicationName(hostEnvironment.ApplicationName); _logger = loggerFactory.CreateLogger(); _cancellationToken = _cancellationTokenSource.Token; + _kubernetesChangesChannel = Channel.CreateUnbounded<(WatchEventType, string, CustomResource?)>(); + _resourceViewModelChangesChannel = Channel.CreateUnbounded(); RunWatchTask(); RunWatchTask(); RunWatchTask(); RunWatchTask(); - _resourceViewModelChangesChannel = Channel.CreateUnbounded>(); - Task.Run(ProcessKubernetesChanges); - _resourceViewModelProcessor = new ViewModelProcessor(_resourceViewModelChangesChannel, _cancellationToken); + _resourceViewModelProcessor = new ViewModelProcessor(_resourceViewModelChangesChannel, _cancellationToken); } public string ApplicationName => _applicationName; - public ViewModelMonitor GetResources() => _resourceViewModelProcessor.GetResourceMonitor(); + public ViewModelMonitor GetResources() => _resourceViewModelProcessor.GetMonitor(); private void RunWatchTask() where T : CustomResource @@ -287,7 +288,7 @@ private async Task ProcessServiceChange(WatchEventType watchEventType, Service s private async Task WriteChange(ResourceViewModel resourceViewModel, ObjectChangeType changeType = ObjectChangeType.Modified) { await _resourceViewModelChangesChannel.Writer.WriteAsync( - new ResourceChanged(changeType, resourceViewModel), _cancellationToken) + new ResourceChange(changeType, resourceViewModel), _cancellationToken) .ConfigureAwait(false); } diff --git a/src/Aspire.Hosting/Dashboard/ViewModelProcessor.cs b/src/Aspire.Hosting/Dashboard/ViewModelProcessor.cs index c3481b5ccc..7affca9aa7 100644 --- a/src/Aspire.Hosting/Dashboard/ViewModelProcessor.cs +++ b/src/Aspire.Hosting/Dashboard/ViewModelProcessor.cs @@ -6,36 +6,36 @@ namespace Aspire.Hosting.Dashboard; -internal sealed class ViewModelProcessor - where TViewModel : ResourceViewModel +internal sealed class ViewModelProcessor { private readonly object _syncLock = new(); - private readonly Channel> _incomingChannel; + private readonly Channel _incomingChannel; private readonly CancellationToken _cancellationToken; - private readonly Dictionary _snapshot = []; - private readonly List>> _subscribedChannels = []; + private readonly Dictionary _snapshot = []; + private readonly List> _subscribedChannels = []; - public ViewModelProcessor(Channel> incomingChannel, CancellationToken cancellationToken) + public ViewModelProcessor(Channel incomingChannel, CancellationToken cancellationToken) { _incomingChannel = incomingChannel; _cancellationToken = cancellationToken; + Task.Run(ProcessChanges, cancellationToken); } - public ViewModelMonitor GetResourceMonitor() + public ViewModelMonitor GetMonitor() { lock (_syncLock) { - var snapshot = _snapshot.Values.ToList(); - var channel = Channel.CreateUnbounded>(); + var channel = Channel.CreateUnbounded(); _subscribedChannels.Add(channel); - var enumerable = new ChangeEnumerable(channel, RemoveChannel); - return new ViewModelMonitor(snapshot, enumerable); + return new ViewModelMonitor( + Snapshot: _snapshot.Values.ToList(), + Watch: new ChangeEnumerable(channel, RemoveChannel)); } } - private void RemoveChannel(Channel> channel) + private void RemoveChannel(Channel channel) { lock (_syncLock) { @@ -43,31 +43,31 @@ private void RemoveChannel(Channel> channel) } } - private sealed class ChangeEnumerable : IAsyncEnumerable> + private sealed class ChangeEnumerable : IAsyncEnumerable { - private readonly Channel> _channel; - private readonly Action>> _disposeAction; + private readonly Channel _channel; + private readonly Action> _disposeAction; - public ChangeEnumerable(Channel> channel, Action>> disposeAction) + public ChangeEnumerable(Channel channel, Action> disposeAction) { _channel = channel; _disposeAction = disposeAction; } - public IAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = default) + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new ChangeEnumerator(_channel, _disposeAction, cancellationToken); } } - private sealed class ChangeEnumerator : IAsyncEnumerator> + private sealed class ChangeEnumerator : IAsyncEnumerator { - private readonly Channel> _channel; - private readonly Action>> _disposeAction; + private readonly Channel _channel; + private readonly Action> _disposeAction; private readonly CancellationToken _cancellationToken; public ChangeEnumerator( - Channel> channel, Action>> disposeAction, CancellationToken cancellationToken) + Channel channel, Action> disposeAction, CancellationToken cancellationToken) { _channel = channel; _disposeAction = disposeAction; @@ -75,7 +75,7 @@ public ChangeEnumerator( Current = default!; } - public ResourceChanged Current { get; private set; } + public ResourceChange Current { get; private set; } public ValueTask DisposeAsync() { @@ -96,7 +96,7 @@ private async Task ProcessChanges() { await foreach (var change in _incomingChannel.Reader.ReadAllAsync(_cancellationToken)) { - List>> outgoingChannels; + List> outgoingChannels; lock (_syncLock) { var resource = change.Resource;