Split out from #976. The reporter saw the same alerts pop up several times, hours after the originating event.
EmailAlertService deduplicates alert emails with an in-memory ConcurrentDictionary<string, DateTime> (_cooldowns, keyed {serverId}:{metricName}) checked against App.EmailCooldownMinutes. It is never persisted, so an app restart clears every cooldown and an alert that fired recently can immediately fire again.
UI acknowledgements (alert_state.json) do persist, but only clear on a genuinely newer event.
Suggested fix
Persist the cooldown state so it survives restarts. The config_alert_log table already records every alert with alert_time, server_id, metric_name — the cooldown check could read the most recent alert_time for the key instead of relying solely on the in-memory dictionary.
Component
Lite. Reported on 2.10.0.
Split out from #976. The reporter saw the same alerts pop up several times, hours after the originating event.
EmailAlertServicededuplicates alert emails with an in-memoryConcurrentDictionary<string, DateTime>(_cooldowns, keyed{serverId}:{metricName}) checked againstApp.EmailCooldownMinutes. It is never persisted, so an app restart clears every cooldown and an alert that fired recently can immediately fire again.UI acknowledgements (
alert_state.json) do persist, but only clear on a genuinely newer event.Suggested fix
Persist the cooldown state so it survives restarts. The
config_alert_logtable already records every alert withalert_time,server_id,metric_name— the cooldown check could read the most recentalert_timefor the key instead of relying solely on the in-memory dictionary.Component
Lite. Reported on 2.10.0.