From 23a4a0093b5d1ea39f76b87f7f8d33fca97df292 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 11 Jun 2026 09:38:58 -0400 Subject: [PATCH] fix(adminui): guard Alerts chip auto-clear against stale-timer race (review) --- .../Components/Pages/Alerts.razor | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Alerts.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Alerts.razor index a2946802..b7927f74 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Alerts.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Alerts.razor @@ -135,6 +135,7 @@ else // Auto-clear timer for the per-row result chip (mirrors DriverStatusPanel): the chip is set in // ShowOpResult and cleared 8 s later so it doesn't persist until the next action. private System.Threading.Timer? _opResultClearTimer; + private object? _opResultClearToken; // Per-row timed-shelve duration (minutes). Keyed by AlarmId so each row's number input is // independent — binding every row to one shared field would couple all the inputs together. @@ -240,13 +241,20 @@ else StateHasChanged(); // Auto-clear the result chip after 8 s (mirrors DriverStatusPanel). System.Threading.Timer // is used (not System.Timers.Timer) so DisposeAsync can drain any in-flight callback. + var token = _opResultClearToken = new object(); _opResultClearTimer?.Dispose(); _opResultClearTimer = new System.Threading.Timer(_ => - { - _opResultMessage = null; - _opResultAlarmId = ""; - InvokeAsync(StateHasChanged); - }, null, TimeSpan.FromSeconds(8), Timeout.InfiniteTimeSpan); + InvokeAsync(() => + { + // Ignore a stale fire: a newer action superseded this timer's chip. (Timer.Dispose does + // not drain an already-queued callback, so without this a stale timer could clear a + // freshly-set result from a different row.) + if (!ReferenceEquals(_opResultClearToken, token)) return; + _opResultMessage = null; + _opResultAlarmId = ""; + StateHasChanged(); + }), + null, TimeSpan.FromSeconds(8), Timeout.InfiniteTimeSpan); } private void OnAlarm(AlarmTransitionEvent evt) =>