fix(ui): reentrancy guard on Health/AlarmSummary poll timers (arch-review S3)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -206,6 +206,7 @@
|
||||
private bool _loading;
|
||||
|
||||
private Timer? _refreshTimer;
|
||||
private readonly ZB.MOM.WW.ScadaBridge.CentralUI.Services.PollGate _pollGate = new();
|
||||
private const int _autoRefreshSeconds = 15;
|
||||
|
||||
// ── Client-side filters ──
|
||||
@@ -282,10 +283,18 @@
|
||||
StopTimer();
|
||||
_refreshTimer = new Timer(_ =>
|
||||
{
|
||||
if (!_pollGate.TryEnter()) return;
|
||||
InvokeAsync(async () =>
|
||||
{
|
||||
await RefreshAsync();
|
||||
StateHasChanged();
|
||||
try
|
||||
{
|
||||
await RefreshAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pollGate.Exit();
|
||||
}
|
||||
});
|
||||
}, null, TimeSpan.FromSeconds(_autoRefreshSeconds), TimeSpan.FromSeconds(_autoRefreshSeconds));
|
||||
}
|
||||
|
||||
@@ -444,6 +444,7 @@
|
||||
private IReadOnlyDictionary<string, SiteHealthState> _siteStates = new Dictionary<string, SiteHealthState>();
|
||||
private Dictionary<string, string> _siteNames = new();
|
||||
private Timer? _refreshTimer;
|
||||
private readonly ZB.MOM.WW.ScadaBridge.CentralUI.Services.PollGate _pollGate = new();
|
||||
private int _autoRefreshSeconds = 10;
|
||||
|
||||
// Notification Outbox headline KPIs, refreshed alongside the site states.
|
||||
@@ -542,10 +543,18 @@
|
||||
|
||||
_refreshTimer = new Timer(_ =>
|
||||
{
|
||||
if (!_pollGate.TryEnter()) return;
|
||||
InvokeAsync(async () =>
|
||||
{
|
||||
await RefreshNow();
|
||||
StateHasChanged();
|
||||
try
|
||||
{
|
||||
await RefreshNow();
|
||||
StateHasChanged();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pollGate.Exit();
|
||||
}
|
||||
});
|
||||
}, null, TimeSpan.FromSeconds(_autoRefreshSeconds), TimeSpan.FromSeconds(_autoRefreshSeconds));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
||||
|
||||
/// <summary>Reentrancy guard for poll timers: a fixed-period Timer whose refresh outlives
|
||||
/// the period stacks overlapping fan-outs against an already-degraded site (arch-review S3).
|
||||
/// Callers TryEnter at tick start and skip the tick when the previous refresh is in flight.</summary>
|
||||
public sealed class PollGate
|
||||
{
|
||||
private int _inFlight;
|
||||
public bool TryEnter() => Interlocked.CompareExchange(ref _inFlight, 1, 0) == 0;
|
||||
public void Exit() => Volatile.Write(ref _inFlight, 0);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests;
|
||||
|
||||
public class PollGateTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryEnter_SecondCallWhileHeld_ReturnsFalse()
|
||||
{
|
||||
var gate = new PollGate();
|
||||
Assert.True(gate.TryEnter());
|
||||
Assert.False(gate.TryEnter());
|
||||
gate.Exit();
|
||||
Assert.True(gate.TryEnter());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user