diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteAlarmAggregatorActor.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteAlarmAggregatorActor.cs
index 869d9886..c1c53432 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteAlarmAggregatorActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteAlarmAggregatorActor.cs
@@ -63,15 +63,15 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
/// True while a coalesced publish is armed (dirty deltas awaiting one tick). Actor-thread only.
private bool _publishPending;
- /// Delay between gRPC reconnection attempts. Settable for tests.
- internal static TimeSpan ReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
+ /// Delay between gRPC reconnection attempts (ctor-injected; production default 5s).
+ private readonly TimeSpan _reconnectDelay;
///
/// How long a freshly-opened gRPC stream must stay up before its retry budget is
- /// considered recovered (mirrors ).
- /// Settable for tests.
+ /// considered recovered (mirrors );
+ /// ctor-injected (production default 60s).
///
- internal static TimeSpan StabilityWindow { get; set; } = TimeSpan.FromSeconds(60);
+ private readonly TimeSpan _stabilityWindow;
private int _retryCount;
private bool _useNodeA = true;
@@ -143,6 +143,10 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
/// into one publish per window (review 02 round 2, N6);
/// restores per-delta publishing (legacy). Seed/reconcile publishes stay immediate.
///
+ /// Delay between gRPC reconnection attempts (production 5s).
+ ///
+ /// How long a fresh gRPC stream must stay up before its retry budget recovers (production 60s).
+ ///
public SiteAlarmAggregatorActor(
string siteIdentifier,
string correlationId,
@@ -152,7 +156,9 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
string grpcNodeAAddress,
string grpcNodeBAddress,
TimeSpan reconcileInterval,
- TimeSpan publishCoalesce)
+ TimeSpan publishCoalesce,
+ TimeSpan reconnectDelay,
+ TimeSpan stabilityWindow)
{
_siteIdentifier = siteIdentifier;
_correlationId = correlationId;
@@ -163,6 +169,8 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
_grpcNodeBAddress = grpcNodeBAddress;
_reconcileInterval = reconcileInterval;
_publishCoalesce = publishCoalesce;
+ _reconnectDelay = reconnectDelay;
+ _stabilityWindow = stabilityWindow;
// Live delta from the site-wide alarm stream (marshalled in via Self.Tell).
// A received delta must NOT reset the retry budget (a flapping stream that
@@ -499,7 +507,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
_grpcCts?.Dispose();
_grpcCts = new CancellationTokenSource();
- Timers.StartSingleTimer(StabilityTimerKey, new GrpcAlarmStreamStable(), StabilityWindow);
+ Timers.StartSingleTimer(StabilityTimerKey, new GrpcAlarmStreamStable(), _stabilityWindow);
var generation = ++_streamGeneration;
var client = _grpcFactory.GetOrCreate(_siteIdentifier, endpoint);
@@ -558,7 +566,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
if (_retryCount == 1)
Self.Tell(new ReconnectAlarmStream());
else
- Timers.StartSingleTimer(ReconnectTimerKey, new ReconnectAlarmStream(), ReconnectDelay);
+ Timers.StartSingleTimer(ReconnectTimerKey, new ReconnectAlarmStream(), _reconnectDelay);
}
private void CleanupGrpc()
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/SiteAlarmLiveCacheService.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/SiteAlarmLiveCacheService.cs
index 568dc538..13ec0c1b 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/SiteAlarmLiveCacheService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/SiteAlarmLiveCacheService.cs
@@ -234,7 +234,9 @@ public sealed class SiteAlarmLiveCacheService : ISiteAlarmLiveCache
grpcA,
grpcB,
_options.LiveAlarmCacheReconcileInterval,
- _options.LiveAlarmCachePublishCoalesce));
+ _options.LiveAlarmCachePublishCoalesce,
+ TimeSpan.FromSeconds(5), // reconnect delay — former ReconnectDelay static default
+ TimeSpan.FromSeconds(60))); // stability window — former StabilityWindow static default
entry.Actor = system.ActorOf(props, $"site-alarm-aggregator-{entry.SiteId}-{Guid.NewGuid():N}");
entry.StartRetryTimer?.Dispose();
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/SiteAlarmAggregatorActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/SiteAlarmAggregatorActorTests.cs
index e4e7b21d..35c09662 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/SiteAlarmAggregatorActorTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/SiteAlarmAggregatorActorTests.cs
@@ -24,10 +24,12 @@ public class SiteAlarmAggregatorActorTests : TestKit
public SiteAlarmAggregatorActorTests() : base(@"akka.loglevel = WARNING")
{
- SiteAlarmAggregatorActor.ReconnectDelay = TimeSpan.FromMilliseconds(50);
- SiteAlarmAggregatorActor.StabilityWindow = TimeSpan.FromSeconds(30);
}
+ // Former process-global static test seams, now ctor-injected per actor (R2 T12).
+ private static readonly TimeSpan TestReconnectDelay = TimeSpan.FromMilliseconds(50);
+ private static readonly TimeSpan TestStabilityWindow = TimeSpan.FromSeconds(30);
+
// ── Test doubles ────────────────────────────────────────────────────────────
///
@@ -146,7 +148,9 @@ public class SiteAlarmAggregatorActorTests : TestKit
var props = Props.Create(() => new SiteAlarmAggregatorActor(
SiteId, "corr-1", seed.Seed, sink.Publish, factory, GrpcNodeA, GrpcNodeB,
reconcileInterval ?? TimeSpan.FromMinutes(10),
- publishCoalesce ?? TimeSpan.Zero));
+ publishCoalesce ?? TimeSpan.Zero,
+ TestReconnectDelay,
+ TestStabilityWindow));
var actor = Sys.ActorOf(props);
return (actor, seed, sink, factory);