fix(adminui): guard Alerts chip auto-clear against stale-timer race (review)

This commit is contained in:
Joseph Doherty
2026-06-11 09:38:58 -04:00
parent 19ec656cdc
commit 23a4a0093b
@@ -135,6 +135,7 @@ else
// Auto-clear timer for the per-row result chip (mirrors DriverStatusPanel): the chip is set in // 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. // ShowOpResult and cleared 8 s later so it doesn't persist until the next action.
private System.Threading.Timer? _opResultClearTimer; private System.Threading.Timer? _opResultClearTimer;
private object? _opResultClearToken;
// Per-row timed-shelve duration (minutes). Keyed by AlarmId so each row's number input is // 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. // independent — binding every row to one shared field would couple all the inputs together.
@@ -240,13 +241,20 @@ else
StateHasChanged(); StateHasChanged();
// Auto-clear the result chip after 8 s (mirrors DriverStatusPanel). System.Threading.Timer // 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. // is used (not System.Timers.Timer) so DisposeAsync can drain any in-flight callback.
var token = _opResultClearToken = new object();
_opResultClearTimer?.Dispose(); _opResultClearTimer?.Dispose();
_opResultClearTimer = new System.Threading.Timer(_ => _opResultClearTimer = new System.Threading.Timer(_ =>
{ InvokeAsync(() =>
_opResultMessage = null; {
_opResultAlarmId = ""; // Ignore a stale fire: a newer action superseded this timer's chip. (Timer.Dispose does
InvokeAsync(StateHasChanged); // not drain an already-queued callback, so without this a stale timer could clear a
}, null, TimeSpan.FromSeconds(8), Timeout.InfiniteTimeSpan); // 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) => private void OnAlarm(AlarmTransitionEvent evt) =>