fix(adminui): fleet page disposes its poll timer asynchronously like hosts

This commit is contained in:
Joseph Doherty
2026-08-16 04:15:05 -04:00
parent 409a58b624
commit 63081d52d8
@@ -12,7 +12,7 @@
@using ZB.MOM.WW.OtOpcUa.Configuration.Enums
@inject IClusterRoleInfo Cluster
@inject IDbContextFactory<OtOpcUaConfigDbContext> DbFactory
@implements IDisposable
@implements IAsyncDisposable
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0">Fleet status</h4>
@@ -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();
}