perf(alarms): memoize CurrentAlarms projection, invalidate on mutation
This commit is contained in:
@@ -34,6 +34,13 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic
|
||||
private readonly Dictionary<string, ActiveAlarmSnapshot> _alarms = new(StringComparer.Ordinal);
|
||||
private readonly List<Subscriber> _subscribers = [];
|
||||
|
||||
// Memoized CurrentAlarms projection, guarded by _sync: the cloned, read-only view of _alarms
|
||||
// handed to the dashboard and the QueryActiveAlarms RPC. Cloning the whole set per read held
|
||||
// _sync — the broadcast lock — for the length of the copy, so a polled dashboard stalled every
|
||||
// ApplyTransition/Broadcast behind it. Null means "not built for the current generation":
|
||||
// every path that writes _alarms must null this under _sync, or readers keep a stale set.
|
||||
private ActiveAlarmSnapshot[]? _currentAlarmsProjection;
|
||||
|
||||
// NEXT-03 dedup tombstones, guarded by _sync: alarm instances whose Clear was synthesized by
|
||||
// the most recent reconcile pass, keyed by reference with the instance's original raise
|
||||
// timestamp as the identity marker. A buffered live Clear for the same instance is a duplicate
|
||||
@@ -93,7 +100,12 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _alarms.Values.Select(alarm => alarm.Clone()).ToArray();
|
||||
// Same clone semantics as an uncached read — callers still get instances no
|
||||
// mutation can leak back into the cache — but built once per alarm-set
|
||||
// generation instead of once per caller.
|
||||
return _currentAlarmsProjection ??= _alarms.Values
|
||||
.Select(alarm => alarm.Clone())
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,6 +434,11 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic
|
||||
if (transition.TransitionKind == AlarmTransitionKind.Clear)
|
||||
{
|
||||
bool wasKnown = _alarms.Remove(reference);
|
||||
if (wasKnown)
|
||||
{
|
||||
_currentAlarmsProjection = null;
|
||||
}
|
||||
|
||||
if (!wasKnown && IsDuplicateOfReconcileClear(reference, transition))
|
||||
{
|
||||
return;
|
||||
@@ -433,6 +450,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic
|
||||
bool duplicate = _alarms.TryGetValue(reference, out ActiveAlarmSnapshot? existing)
|
||||
&& IsDuplicateOfCachedState(existing, snapshot);
|
||||
_alarms[reference] = snapshot;
|
||||
_currentAlarmsProjection = null;
|
||||
if (duplicate)
|
||||
{
|
||||
return;
|
||||
@@ -650,6 +668,8 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic
|
||||
{
|
||||
_alarms[incoming.Key] = incoming.Value;
|
||||
}
|
||||
|
||||
_currentAlarmsProjection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,6 +716,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic
|
||||
lock (_sync)
|
||||
{
|
||||
_alarms.Clear();
|
||||
_currentAlarmsProjection = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user