From f4b96214936bd3f55e210abf5ea8f825fa3ea4ca Mon Sep 17 00:00:00 2001 From: Hublack <71221641+HublackBE@users.noreply.github.com> Date: Fri, 9 Jan 2026 11:02:02 +0100 Subject: [PATCH 1/2] Handle null GetAllUnitsAsync response with user error Replaced exception throw on null GetAllUnitsAsync response with a user-facing error message via ShowErrorInfoBarAsync. This prevents application crashes and provides clearer feedback to the user when the agent returns no data. --- .../srvMgnt/Views/ServicesPage.xaml.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/paradigm-ehb.CommandCenter.WinUI/srvMgnt/Views/ServicesPage.xaml.cs b/src/paradigm-ehb.CommandCenter.WinUI/srvMgnt/Views/ServicesPage.xaml.cs index 0b0d243..d78cd8d 100644 --- a/src/paradigm-ehb.CommandCenter.WinUI/srvMgnt/Views/ServicesPage.xaml.cs +++ b/src/paradigm-ehb.CommandCenter.WinUI/srvMgnt/Views/ServicesPage.xaml.cs @@ -360,7 +360,8 @@ private async Task LoadAllServices(CancellationToken cancellationToken = default GetUnitsReply? response = await client.Service.GetAllUnitsAsync(request: new GetUnitsRequest(), cancellationToken: cancellationToken); if (response is null) { - throw new InvalidOperationException("Received null response from ActionAsync."); + await ShowErrorInfoBarAsync("Failed to retrieve services: received null response from agent."); + return; } foreach (LoadedUnit? unit in response.Units) From 70498603bc34840615372ab9aae4e9143f8352f9 Mon Sep 17 00:00:00 2001 From: Hublack <71221641+HublackBE@users.noreply.github.com> Date: Fri, 9 Jan 2026 11:29:22 +0100 Subject: [PATCH 2/2] Implement async disposal for AgentClient and cleanup on nav AgentClient now supports IAsyncDisposable for proper async resource cleanup, including gRPC channel shutdown. ServerMainPage.xaml.cs disposes and deregisters AgentClient asynchronously on navigation, improving resource management and preventing leaks. --- .../Models/AgentClient.cs | 31 +++++++++++++++++-- .../srvMgnt/ServerMainPage.xaml.cs | 9 ++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/paradigm-ehb.CommandCenter.Core/Models/AgentClient.cs b/src/paradigm-ehb.CommandCenter.Core/Models/AgentClient.cs index 8501f61..4c5fe1d 100644 --- a/src/paradigm-ehb.CommandCenter.Core/Models/AgentClient.cs +++ b/src/paradigm-ehb.CommandCenter.Core/Models/AgentClient.cs @@ -8,7 +8,7 @@ namespace paradigm_ehb.CommandCenter.Core.Models { - public sealed class AgentClient : IDisposable + public sealed class AgentClient : IDisposable, IAsyncDisposable { private bool disposedValue; private AgentHealthWatcher? _healthWatcher; @@ -63,6 +63,7 @@ private void Dispose(bool disposing) { StopHealthWatch(); _healthWatcher?.Dispose(); + Channel.ShutdownAsync().Wait(); Channel?.Dispose(); } catch @@ -77,7 +78,33 @@ private void Dispose(bool disposing) public void Dispose() { Dispose(disposing: true); - GC.SuppressFinalize(this); + } + + private async Task DisposeAsync(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + try + { + StopHealthWatch(); + _healthWatcher?.Dispose(); + await Channel.ShutdownAsync().ConfigureAwait(false); + Channel?.Dispose(); + } + catch + { + // ignore + } + } + disposedValue = true; + } + } + + public async ValueTask DisposeAsync() + { + await DisposeAsync(true); } } } diff --git a/src/paradigm-ehb.CommandCenter.WinUI/srvMgnt/ServerMainPage.xaml.cs b/src/paradigm-ehb.CommandCenter.WinUI/srvMgnt/ServerMainPage.xaml.cs index 60f4f38..01e789d 100644 --- a/src/paradigm-ehb.CommandCenter.WinUI/srvMgnt/ServerMainPage.xaml.cs +++ b/src/paradigm-ehb.CommandCenter.WinUI/srvMgnt/ServerMainPage.xaml.cs @@ -38,6 +38,15 @@ protected override void OnNavigatedTo(NavigationEventArgs e) _ = InitializeForNavigationAsync(e); } + protected override async void OnNavigatedFrom(NavigationEventArgs e) + { + base.OnNavigatedFrom(e); + + AgentClient? agentClient = await _agentClientRegistry.GetAsync(serverObj.Id); + if (agentClient is not null) await agentClient.DisposeAsync(); + await _agentClientRegistry.DeregisterAsync(serverObj.Id); + } + private async Task InitializeForNavigationAsync(NavigationEventArgs e) { try