From 63081d52d82621849aed2a42b9fb0c5fa45832f8 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 16 Aug 2026 04:15:05 -0400 Subject: [PATCH] fix(adminui): fleet page disposes its poll timer asynchronously like hosts --- .../Components/Pages/Fleet.razor | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Fleet.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Fleet.razor index 629e76f1..d3a70049 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Fleet.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Fleet.razor @@ -12,7 +12,7 @@ @using ZB.MOM.WW.OtOpcUa.Configuration.Enums @inject IClusterRoleInfo Cluster @inject IDbContextFactory DbFactory -@implements IDisposable +@implements IAsyncDisposable

Fleet status

@@ -110,8 +110,8 @@ else // next one. A manual Refresh landing on top of an in-flight tick is a no-op by the same guard. private int _tickRunning; - // Page-lifetime cancellation: cancelled in Dispose() so a tick in flight when the circuit goes - // away aborts its query instead of running it to completion against a component that's gone. + // Page-lifetime cancellation: cancelled in DisposeAsync() so a tick in flight when the circuit + // goes away aborts its query instead of running it to completion against a component that's gone. private readonly CancellationTokenSource _cts = new(); protected override async Task OnInitializedAsync() @@ -184,12 +184,14 @@ else private static string StatusLabel(NodeDeploymentStatus? status) => status?.ToString() ?? "waiting"; - public void Dispose() + public async ValueTask DisposeAsync() { - // Cancel BEFORE disposing the timer so a tick already in flight aborts its query rather than - // running to completion; the CTS is disposed last, once nothing new can observe the token. + // Cancel before draining so a tick already in flight aborts its queries rather than running + // them to completion against a component that's gone. _cts.Cancel(); - _timer?.Dispose(); + // Drain the timer so an in-flight callback can't touch a component that's already gone + // (System.Threading.Timer's async dispose awaits any in-flight callback — .NET 6+). + if (_timer is not null) await _timer.DisposeAsync(); _cts.Dispose(); }