test(GWC-26): deflake ApplyReconcileBroadcastsAcknowledgeDelta
Seeding the cache with a live Raise transition raced the first reconcile: when the reconcile snapshot populated the cache first, the still-buffered live Raise was applied — and broadcast — after the test's feed subscriber had registered, so the exactly-one-transition assertion saw two. Seed through a reconcile pass instead (forced by a provider-mode probe, as the acked step already did) so no live transition is ever in flight. Verified: 5 consecutive full alarm-monitor runs green, and the test still fails (timeout) with the ApplyReconcile acked-delta branch removed.
This commit is contained in:
@@ -110,15 +110,19 @@ public sealed class GatewayAlarmMonitorAttachOrderTests
|
|||||||
await monitor.StartAsync(cts.Token);
|
await monitor.StartAsync(cts.Token);
|
||||||
await sessions.WaitForSubscribeStartAsync(WaitTimeout);
|
await sessions.WaitForSubscribeStartAsync(WaitTimeout);
|
||||||
|
|
||||||
// Seed the cache with an active (unacked) alarm and keep the reconcile snapshot in
|
// Seed the cache with an active (unacked) alarm through a reconcile rather than a live
|
||||||
// agreement, so a periodic reconcile pass cannot clear it out from under the test.
|
// transition: a buffered live transition could still be in flight when the cache first
|
||||||
|
// shows the alarm, and would then broadcast to the reader below and pollute the
|
||||||
|
// exactly-one-transition assertion. A provider-mode event forces the reconcile
|
||||||
|
// immediately, so the test never waits on the periodic timer.
|
||||||
sessions.SetReconcileSnapshot(Snapshot(AlarmConditionState.Active));
|
sessions.SetReconcileSnapshot(Snapshot(AlarmConditionState.Active));
|
||||||
sessions.EmitEvent(Transition(1, AlarmTransitionKind.Raise));
|
sessions.EmitEvent(ProviderModeProbe(1));
|
||||||
await WaitUntilAsync(
|
await WaitUntilAsync(
|
||||||
() => monitor.CurrentAlarms.Any(alarm => alarm.AlarmFullReference == AlarmReference),
|
() => monitor.CurrentAlarms.Any(alarm => alarm.AlarmFullReference == AlarmReference
|
||||||
|
&& alarm.CurrentState == AlarmConditionState.Active),
|
||||||
WaitTimeout);
|
WaitTimeout);
|
||||||
|
|
||||||
// Subscribe AFTER the raise: this reader's snapshot carries the alarm, so every
|
// Subscribe AFTER the seed: this reader's snapshot carries the alarm, so every
|
||||||
// transition it observes from here on is a reconcile-derived broadcast.
|
// transition it observes from here on is a reconcile-derived broadcast.
|
||||||
List<AlarmFeedMessage> received = [];
|
List<AlarmFeedMessage> received = [];
|
||||||
TaskCompletionSource snapshotComplete = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
TaskCompletionSource snapshotComplete = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
@@ -129,17 +133,7 @@ public sealed class GatewayAlarmMonitorAttachOrderTests
|
|||||||
// The worker now reports the same alarm acknowledged. A provider-mode event forces an
|
// The worker now reports the same alarm acknowledged. A provider-mode event forces an
|
||||||
// immediate reconcile pass so the test does not wait on the periodic timer.
|
// immediate reconcile pass so the test does not wait on the periodic timer.
|
||||||
sessions.SetReconcileSnapshot(Snapshot(AlarmConditionState.ActiveAcked));
|
sessions.SetReconcileSnapshot(Snapshot(AlarmConditionState.ActiveAcked));
|
||||||
sessions.EmitEvent(new MxEvent
|
sessions.EmitEvent(ProviderModeProbe(2));
|
||||||
{
|
|
||||||
Family = MxEventFamily.OnAlarmProviderModeChanged,
|
|
||||||
WorkerSequence = 2,
|
|
||||||
OnAlarmProviderModeChanged = new OnAlarmProviderModeChangedEvent
|
|
||||||
{
|
|
||||||
Mode = AlarmProviderMode.Alarmmgr,
|
|
||||||
Reason = "probe",
|
|
||||||
At = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
AlarmFeedMessage acknowledge = await WaitForAsync(
|
AlarmFeedMessage acknowledge = await WaitForAsync(
|
||||||
received,
|
received,
|
||||||
@@ -238,6 +232,20 @@ public sealed class GatewayAlarmMonitorAttachOrderTests
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A no-op provider-mode event. The monitor forces an immediate reconcile after every one,
|
||||||
|
// which is how these tests drive a reconcile pass without waiting on the periodic timer.
|
||||||
|
private static MxEvent ProviderModeProbe(ulong sequence) => new()
|
||||||
|
{
|
||||||
|
Family = MxEventFamily.OnAlarmProviderModeChanged,
|
||||||
|
WorkerSequence = sequence,
|
||||||
|
OnAlarmProviderModeChanged = new OnAlarmProviderModeChangedEvent
|
||||||
|
{
|
||||||
|
Mode = AlarmProviderMode.Alarmmgr,
|
||||||
|
Reason = "probe",
|
||||||
|
At = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
private static ActiveAlarmSnapshot Snapshot(AlarmConditionState state) => new()
|
private static ActiveAlarmSnapshot Snapshot(AlarmConditionState state) => new()
|
||||||
{
|
{
|
||||||
AlarmFullReference = AlarmReference,
|
AlarmFullReference = AlarmReference,
|
||||||
|
|||||||
Reference in New Issue
Block a user