79d6b0a4b2
- DriverHostActor.StopChild removes the driver's _lastReprovisionUtc stamp (and its deferred-reprovision timer): the map grew per removed id and a re-added driver inherited a backoff it never earned (02/L2). - Stale now registers explicit drops for AttributeValueBatchPublished, AttributeAlarmPublished, ArtifactForApply and ApplyWedged instead of letting them hit Unhandled (02/H5). - DbHealthProbeActor runs its SELECT on the thread pool and pipes the outcome back (single-flight), with a 2 s command timeout on relational providers, so a hung SQL Server no longer pins the actor thread and the cached GetStatus answer stays reachable (02/P7). - TelemetryLocalHub eviction tombstones are timestamped and swept past a 10 min TTL on the next Evict; a TimeProvider ctor is the test seam (02/L4). - Tests: ValuePublishes.Single helper replaces the per-value AttributeValuePublished in CalculationDependencyFlowTests (02/U4, test half; the production receive is owned by Lane E). - R05/8 audit: PollGroupEngine.Unsubscribe has no production caller; every driver already uses UnsubscribeAsync (no change). Refs #731 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
64 lines
3.6 KiB
C#
64 lines
3.6 KiB
C#
using Akka.Actor;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
|
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;
|
|
|
|
/// <summary>
|
|
/// Review 02 L2: <c>DriverHostActor._lastReprovisionUtc</c> was never pruned — a driver removed by a deploy
|
|
/// after a supervision crash kept its stamp, so the map grew with every id ever reprovisioned-then-removed,
|
|
/// and (the observable half) a driver removed and later re-added inherited the OLD stamp: its first crash
|
|
/// after the re-add was deferred by the backoff as if it were crash-looping, although the new incarnation had
|
|
/// never crashed. <c>StopChild</c> now drops the stamp.
|
|
/// </summary>
|
|
public sealed class DriverHostActorReprovisionStampTests : RuntimeActorTestBase
|
|
{
|
|
private static readonly RevisionHash RevA = RevisionHash.Parse(new string('a', 64));
|
|
private static readonly RevisionHash RevB = RevisionHash.Parse(new string('b', 64));
|
|
private static readonly RevisionHash RevC = RevisionHash.Parse(new string('c', 64));
|
|
private static readonly NodeId TestNode = NodeId.Parse("node-1:4053");
|
|
private const string DriverId = "DI-1";
|
|
private const string SpeedPath = "Plant/Modbus/dev1/speed";
|
|
|
|
[Fact]
|
|
public void A_driver_removed_and_re_added_does_not_inherit_the_old_incarnations_reprovision_stamp()
|
|
{
|
|
var db = NewInMemoryDbFactory();
|
|
// Far longer than the presence budget below: an inherited stamp would defer the re-added driver's
|
|
// reprovision past it, which is the failure this test reports.
|
|
var backoff = TimeSpan.FromSeconds(60);
|
|
var factory = new RecordingSubscribableDriverFactory("Modbus");
|
|
var withDriver = new[] { (DriverId, "Modbus", "dev1", "speed", "Float", false) };
|
|
var none = Array.Empty<(string, string, string, string, string?)>();
|
|
|
|
var revA = V3ArtifactSeeder.SeedV3Deployment(db, RevA, withDriver, none, enabled: true);
|
|
var (host, _, _, coordinator) = V3ArtifactSeeder.SpawnHostAndApplyWithCoordinator(
|
|
this, db, TestNode, revA, RevA, factory, reprovisionBackoff: backoff);
|
|
AwaitAssert(() => factory.Created.Count.ShouldBe(1), PresenceBudget);
|
|
AwaitAssert(() => factory.Created[0].SubscribeCount.ShouldBe(1), PresenceBudget);
|
|
|
|
// Crash 1 on the first incarnation: reprovisioned immediately, which stamps the id.
|
|
factory.Created[0].FireMalformedDataChange(SpeedPath);
|
|
AwaitAssert(() => factory.Created[0].InitializeCount.ShouldBe(2), PresenceBudget);
|
|
|
|
// Deploy B removes the driver (StopChild), deploy C re-adds it: a brand-new driver object.
|
|
var revB = V3ArtifactSeeder.SeedV3Deployment(
|
|
db, RevB, Array.Empty<(string, string, string, string, string, bool)>(), none, enabled: true);
|
|
V3ArtifactSeeder.Dispatch(host, coordinator, revB, RevB);
|
|
var revC = V3ArtifactSeeder.SeedV3Deployment(db, RevC, withDriver, none, enabled: true);
|
|
V3ArtifactSeeder.Dispatch(host, coordinator, revC, RevC);
|
|
AwaitAssert(() => factory.Created.Count.ShouldBe(2), PresenceBudget);
|
|
var reAdded = factory.Created[1];
|
|
AwaitAssert(() => reAdded.SubscribeCount.ShouldBe(1), PresenceBudget);
|
|
|
|
// Its FIRST crash must reprovision immediately — it has no history of its own.
|
|
reAdded.FireMalformedDataChange(SpeedPath);
|
|
AwaitAssert(
|
|
() => reAdded.InitializeCount.ShouldBe(
|
|
2, "the re-added driver's first reprovision was deferred by the REMOVED incarnation's stamp"),
|
|
duration: TimeSpan.FromSeconds(5));
|
|
}
|
|
}
|