fix(site-runtime): watch every Instance Actor; unexpected termination evicts the dead ref from routing (S6)
This commit is contained in:
+51
-2
@@ -10,6 +10,7 @@ using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
|
||||
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors;
|
||||
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Persistence;
|
||||
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
||||
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.TestSupport;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Actors;
|
||||
@@ -45,7 +46,8 @@ public class DeploymentManagerRedeployTests : TestKit, IDisposable
|
||||
try { File.Delete(_dbFile); } catch { /* cleanup */ }
|
||||
}
|
||||
|
||||
private IActorRef CreateDeploymentManager(ISiteHealthCollector? healthCollector = null)
|
||||
private IActorRef CreateDeploymentManager(
|
||||
ISiteHealthCollector? healthCollector = null, IServiceProvider? serviceProvider = null)
|
||||
{
|
||||
return ActorOf(Props.Create(() => new DeploymentManagerActor(
|
||||
_storage,
|
||||
@@ -57,9 +59,31 @@ public class DeploymentManagerRedeployTests : TestKit, IDisposable
|
||||
null,
|
||||
null,
|
||||
healthCollector,
|
||||
null)));
|
||||
serviceProvider)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a config that deserializes and compiles cleanly (so it passes the deploy
|
||||
/// compile gate) but whose script's canonical name contains a space — illegal as an
|
||||
/// Akka actor name, so <c>Context.ActorOf(props, "script-dies on init")</c> throws
|
||||
/// <c>InvalidActorNameException</c> during the Instance Actor's PreStart. The failure
|
||||
/// surfaces as an <c>ActorInitializationException</c> the DM decider stops — the exact
|
||||
/// "Instance Actor dies during init" S6 scenario.
|
||||
/// </summary>
|
||||
private static string CtorThrowingConfig(string instanceName) =>
|
||||
JsonSerializer.Serialize(new FlattenedConfiguration
|
||||
{
|
||||
InstanceUniqueName = instanceName,
|
||||
Attributes =
|
||||
[
|
||||
new ResolvedAttribute { CanonicalName = "TestAttr", Value = "1", DataType = "Int32" }
|
||||
],
|
||||
Scripts =
|
||||
[
|
||||
new ResolvedScript { CanonicalName = "dies on init", Code = "return 1;" }
|
||||
]
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Minimal fake that records the most recent deployed-instance count.
|
||||
/// </summary>
|
||||
@@ -311,4 +335,29 @@ public class DeploymentManagerRedeployTests : TestKit, IDisposable
|
||||
// not a new instance, so the in-memory counter must not drift upward.
|
||||
Assert.Equal(1, health.LastDeployedCount);
|
||||
}
|
||||
|
||||
// ── S6: an Instance Actor that dies during init must not linger as a dead ref ──
|
||||
|
||||
[Fact]
|
||||
public async Task InstanceActorInitFailure_OnStartup_EvictsDeadRefAndLogsSiteEvent()
|
||||
{
|
||||
// A stored config that compiles cleanly but whose Instance Actor cannot
|
||||
// initialize (illegal child actor name → ActorInitializationException → Stop).
|
||||
// The DM watches every Instance Actor, so on the unexpected Termination it must
|
||||
// evict the dead ref from the routing map and log a `deployment` Error event
|
||||
// rather than leaving a routing entry that points at a stopped actor (S6).
|
||||
await _storage.StoreDeployedConfigAsync(
|
||||
"poison", CtorThrowingConfig("poison"), "d1", "h1", true);
|
||||
|
||||
var siteLog = new FakeSiteEventLogger();
|
||||
CreateDeploymentManager(serviceProvider: new SingleServiceProvider(siteLog));
|
||||
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
Assert.Contains(siteLog.OfType("deployment"), r =>
|
||||
r.Severity == "Error" &&
|
||||
r.InstanceId == "poison" &&
|
||||
r.Message.Contains("terminated unexpectedly", StringComparison.OrdinalIgnoreCase));
|
||||
}, TimeSpan.FromSeconds(10));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user