perf(alarms): memoize CurrentAlarms projection, invalidate on mutation

This commit is contained in:
Joseph Doherty
2026-08-15 12:20:51 -04:00
parent ca34a2d65d
commit f1e26fed4f
2 changed files with 71 additions and 1 deletions
@@ -282,6 +282,55 @@ public sealed class GatewayAlarmMonitorAttachOrderTests
await monitor.StopAsync(CancellationToken.None);
}
/// <summary>
/// <see cref="GatewayAlarmMonitor.CurrentAlarms"/> clones the whole active-alarm set under
/// the broadcast lock, so rebuilding it per read stalls every transition and broadcast
/// behind the copy once the dashboard polls a large alarm set. The projection is memoized
/// for as long as the set is unchanged, and every mutation must invalidate it — a stale
/// projection would hide live transitions from the dashboard and the QueryActiveAlarms RPC.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CurrentAlarmsProjectionIsMemoizedUntilTheAlarmSetChanges()
{
using GatewayMetrics metrics = new();
await using FakeSessionManager sessions = new();
using GatewayAlarmMonitor monitor = CreateMonitor(sessions, metrics);
using CancellationTokenSource cts = new();
await monitor.StartAsync(cts.Token);
await sessions.WaitForSubscribeStartAsync(WaitTimeout);
// Seed through a reconcile (forced by a provider-mode probe) so the cache holds one
// unacked alarm and no further mutation is in flight.
sessions.SetReconcileSnapshot(Snapshot(AlarmConditionState.Active));
sessions.EmitEvent(ProviderModeProbe(1));
await WaitUntilAsync(
() => monitor.CurrentAlarms.Any(alarm => alarm.AlarmFullReference == AlarmReference
&& alarm.CurrentState == AlarmConditionState.Active),
WaitTimeout);
IReadOnlyList<ActiveAlarmSnapshot> first = monitor.CurrentAlarms;
Assert.Same(first, monitor.CurrentAlarms);
// A live Acknowledge replaces the cached snapshot, so the next read must rebuild.
sessions.EmitEvent(Transition(2, AlarmTransitionKind.Acknowledge));
await WaitUntilAsync(
() => monitor.CurrentAlarms.Any(alarm => alarm.AlarmFullReference == AlarmReference
&& alarm.CurrentState == AlarmConditionState.ActiveAcked),
WaitTimeout);
IReadOnlyList<ActiveAlarmSnapshot> second = monitor.CurrentAlarms;
Assert.NotSame(first, second);
Assert.Same(second, monitor.CurrentAlarms);
// The pre-transition projection is a snapshot of the old generation, not a live view.
Assert.Equal(AlarmConditionState.Active, Assert.Single(first).CurrentState);
await cts.CancelAsync();
await monitor.StopAsync(CancellationToken.None);
}
private static GatewayAlarmMonitor CreateMonitor(FakeSessionManager sessions, GatewayMetrics metrics)
{
AlarmsOptions options = new()