feat(historian-gateway): feed historized refs to the recorder on deploy (close continuous-historization ref-feed gap)
v2-ci / build (pull_request) Failing after 39s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (pull_request) Has been skipped
v2-ci / build (pull_request) Failing after 39s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (pull_request) Has been skipped
The ContinuousHistorizationRecorder was spawned with an EMPTY historized-ref set, so it registered interest in nothing and historized nothing. This feeds it the currently-historized tag refs on every address-space deploy/redeploy so its DependencyMuxActor interest converges to exactly the historized set (the same refs the EnsureTags provisioning hook resolves: override-or-FullName). Design — delta convergence (the plan is a pure DIFF): - New seam IHistorizedTagSubscriptionSink (Core.Abstractions/Historian) with a Null no-op singleton, mirroring how IHistorianProvisioning decouples the T15 hook. AddressSpaceApplier gains a DEFAULTED ctor param (Null sink) so all ~80 existing call sites + the production site compile unchanged. - Apply() only ever sees a plan diff (an incremental/surgical apply carries a delta, not the full set), so the applier feeds an add/remove DELTA computed from AddedEquipmentTags / RemovedEquipmentTags / ChangedEquipmentTags. The recorder keeps the full set and re-registers it. The feed is a single non-blocking Tell behind the sink, wrapped in try/catch so a faulting feed never blocks or breaks a deploy (same discipline as the provisioning hook). - Recorder.UpdateHistorizedRefs(added, removed) converges the tracked set, then — only when it actually changed — sends ONE RegisterInterest with the full set (the mux's RegisterInterest is a full-REPLACE) or one UnregisterInterest when it drains to empty (the mux has no per-ref unregister). An unchanged delta is a no-op (no mux churn). - DI: the recorder is now spawned BEFORE the applier so the adapter (ActorHistorizedTagSubscriptionSink) can wrap its IActorRef; the Null sink is used when continuous historization is off/unwired. Tests: recorder convergence (add-from-empty, add+remove converge, idempotent, drain-to-empty unregisters); applier feeds resolved added refs, removed+renamed deltas, and survives a throwing sink. Build clean (0 warnings on touched projects); Runtime/OpcUaServer/Gateway/AdminUI suites green. Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
+48
@@ -0,0 +1,48 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Server-side feed that keeps the continuous-historization recorder's set of historized tag refs
|
||||
/// in step with the deployed address space. The <c>AddressSpaceApplier</c> (in the
|
||||
/// OpcUaServer layer) calls this on every deploy with the add/remove DELTA of historized refs the
|
||||
/// plan changes — the applier only ever sees a diff (an incremental/surgical apply carries a delta,
|
||||
/// not the full set), so the recorder behind this seam keeps the full set and converges it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The feed is <b>non-blocking</b> and best-effort: the production adapter is a single
|
||||
/// fire-and-forget actor <c>Tell</c>, so it never blocks the OPC UA publish thread the applier runs
|
||||
/// on, and the applier wraps the call so a faulting feed can never break a deploy. The applier
|
||||
/// references this abstraction (not the Runtime recorder) so the OpcUaServer layer keeps no
|
||||
/// dependency on Akka / the actor system — exactly mirroring how <see cref="IHistorianProvisioning"/>
|
||||
/// decouples the EnsureTags provisioning hook.
|
||||
/// </remarks>
|
||||
public interface IHistorizedTagSubscriptionSink
|
||||
{
|
||||
/// <summary>
|
||||
/// Converge the recorder's historized-ref interest by an add/remove delta. The refs are
|
||||
/// resolved EXACTLY as the EnsureTags provisioning hook resolves them (a non-alarm historized
|
||||
/// value variable's <c>HistorianTagname</c> override, else its driver-side <c>FullName</c>).
|
||||
/// The recorder applies the delta to its tracked full set and re-registers mux interest only
|
||||
/// when the set actually changes.
|
||||
/// </summary>
|
||||
/// <param name="added">Historized refs newly historized by this deploy (added/changed-into tags).</param>
|
||||
/// <param name="removed">Historized refs no longer historized by this deploy (removed/changed-out tags).</param>
|
||||
void UpdateHistorizedRefs(IReadOnlyList<string> added, IReadOnlyList<string> removed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No-op <see cref="IHistorizedTagSubscriptionSink"/> — the applier's safe default when continuous
|
||||
/// historization is disabled or unwired (no recorder to feed). Every call is a no-op and never
|
||||
/// touches an actor system.
|
||||
/// </summary>
|
||||
public sealed class NullHistorizedTagSubscriptionSink : IHistorizedTagSubscriptionSink
|
||||
{
|
||||
/// <summary>The shared singleton instance.</summary>
|
||||
public static readonly NullHistorizedTagSubscriptionSink Instance = new();
|
||||
|
||||
private NullHistorizedTagSubscriptionSink() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateHistorizedRefs(IReadOnlyList<string> added, IReadOnlyList<string> removed)
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user