diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs index 98040273..7c8f5b3d 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs @@ -101,10 +101,10 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers /// state — a no-op when already Reconnecting or Connecting. /// public sealed record ForceReconnect; - /// One driver-published value. NOT sent to the parent by the subscription path any more — the - /// actor batches every data change into an . Still live as the - /// per-value message the parent fans onward (the dependency mux consumes it, and - /// DriverHostActor.FanCommsLossToDependencyMux fabricates it). + /// One driver-published value. No production sender remains — the subscription path + /// batches every data change into an , and the comms-loss fan + /// (DriverHostActor.FanCommsLossToDependencyMux), which was the last sender to fabricate this + /// message, now fabricates a batch too. The dependency mux still accepts it for tests and direct senders. /// is the driver's full OPC UA status code including its sub-code /// (#519) — not a severity bucket. Classify it with and friends rather /// than comparing it to . diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/ServiceCollectionExtensions.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/ServiceCollectionExtensions.cs index a5601f48..ee611e63 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/ServiceCollectionExtensions.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/ServiceCollectionExtensions.cs @@ -402,7 +402,7 @@ public static class ServiceCollectionExtensions } // Dependency mux must be spawned before DriverHostActor so the host can forward - // AttributeValuePublished into it from the very first driver spawn. + // AttributeValueBatchPublished into it from the very first driver spawn. var mux = system.ActorOf(DependencyMuxActor.Props(), DependencyMuxActorName); registry.Register(mux); diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/VirtualTags/DependencyMuxActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/VirtualTags/DependencyMuxActor.cs index 312d009e..fe57da4e 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/VirtualTags/DependencyMuxActor.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/VirtualTags/DependencyMuxActor.cs @@ -5,7 +5,7 @@ using ZB.MOM.WW.OtOpcUa.Runtime.Drivers; namespace ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags; /// -/// Per-node fan-out router from +/// Per-node fan-out router from /// to interested instances. VirtualTagActor sends /// on start-up listing the tag refs it depends on; the mux /// keeps a map of tagRef → Set<IActorRef> and on every ingress forwards each @@ -16,11 +16,12 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags; /// node). The ONLY driver ingress is the batched /// — one envelope per driver /// production burst, which the mux filters per item. The per-value -/// receive stays for the comms-loss fan -/// ALONE: DriverHostActor.FanCommsLossToDependencyMux fabricates that message and Tells this -/// mux directly, never through the host's own receive. +/// receive has no production sender left +/// at all: the comms-loss fan (DriverHostActor.FanCommsLossToDependencyMux) was its last +/// one and now fabricates an AttributeValueBatchPublished instead. The receive is kept for +/// tests and direct senders — treat it as dead in production. /// -/// The mux emits ONLY batches (the comms-loss fan as a single-item one), so a subscriber's +/// The mux emits ONLY batches, so a subscriber's /// batch receive is its complete hot path. Every subscriber keeps a per-value /// receive as well, for direct senders and tests. /// The mux is deliberately not a DPS subscriber — virtual-tag evaluation is local to each node and @@ -115,8 +116,8 @@ public sealed class DependencyMuxActor : ReceiveActor { // A single-item batch, so the mux emits exactly ONE message shape and every subscriber needs // only the batch receive on its hot path. One list per subscriber: batch ownership transfers - // to the receiver, so a shared list must never cross mailboxes. This is the comms-loss fan - // (one ref, on a driver disconnect), not the steady value path — the extra allocation is noise. + // to the receiver, so a shared list must never cross mailboxes. Nothing in production reaches + // this path (see the class doc) — the extra allocation is noise on a test-only seam. sub.Tell(new VirtualTagActor.DependencyValueBatch(new[] { dep })); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorArtifactCacheTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorArtifactCacheTests.cs index a24ac9cf..94302e10 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorArtifactCacheTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorArtifactCacheTests.cs @@ -143,7 +143,7 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase public void AThrowingCache_DoesNotFailTheApply() { // By the time the cache runs, an Applied ACK has already gone to the coordinator. If a cache - // failure escaped, ApplyAndAck's catch would send a SECOND, contradictory Failed ACK for a + // failure escaped, HandleArtifactForApply's catch would send a SECOND, contradictory Failed ACK for a // deployment the fleet already believes is live. var db = NewInMemoryDbFactory(); var (deploymentId, _) = SeedDeployment(db, RevA, clusterId: null); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorAsyncCacheWriteTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorAsyncCacheWriteTests.cs index a0857409..1a2c6c9f 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorAsyncCacheWriteTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorAsyncCacheWriteTests.cs @@ -23,7 +23,7 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers; /// /// /// -/// The ACK was never the thing at risk: ApplyAndAck sends it BEFORE it caches, so a +/// The ACK was never the thing at risk: HandleArtifactForApply sends it BEFORE it caches, so a /// blocking store still acked promptly. What it blocked was everything after — the host /// could not dequeue a driver value burst, a diagnostics request or the next dispatch until /// SQLite was done. So the load-bearing assertion here is that the actor answers an unrelated diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorLiveValueTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorLiveValueTests.cs index 7a3a0052..d517c8ca 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorLiveValueTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorLiveValueTests.cs @@ -31,7 +31,8 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers; /// Drives a real apply through : the seeded artifact carries the v3 /// raw-tag chain (RawFolder → DriverInstance(RawFolderId) → Device → Tag) + an optional /// UnsTagReference projecting each raw tag into an equipment. DispatchDeployment triggers the -/// ApplyAndAck → PushDesiredSubscriptions pass that builds the map; the sink and mux are +/// BeginDirectApply → HandleArtifactForApply → PushDesiredSubscriptionsFromParsed pass that +/// builds the map; the sink and mux are /// injected as s. /// ///