From b6d2803ff6b700ec54f331aa7c03a00a4fb2b7af Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 19 Apr 2026 07:18:55 -0400 Subject: [PATCH] =?UTF-8?q?Phase=206.1=20Stream=20A=20=E2=80=94=20switch?= =?UTF-8?q?=20pipeline=20keys=20from=20Guid=20to=20string=20to=20match=20I?= =?UTF-8?q?Driver.DriverInstanceId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IDriver.DriverInstanceId is declared as string in Core.Abstractions; keeping the pipeline key as Guid meant every call site would need .ToString() / Guid.Parse at the boundary. Switching the Resilience types to string removes that friction and lets OtOpcUaServer pass driver.DriverInstanceId directly to the builder in the upcoming server-dispatch wiring PR. - DriverResiliencePipelineBuilder.GetOrCreate + Invalidate + PipelineKey - CapabilityInvoker.ctor + _driverInstanceId field Tests: all 48 Core.Tests still pass. The Invalidate test's keepId / dropId now use distinct "drv-keep" / "drv-drop" literals (previously both were distinct Guid.NewGuid() values, which the sed-driven refactor had collapsed to the same literal — caught pre-commit). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Resilience/CapabilityInvoker.cs | 4 ++-- .../DriverResiliencePipelineBuilder.cs | 6 ++--- .../Resilience/CapabilityInvokerTests.cs | 2 +- .../DriverResiliencePipelineBuilderTests.cs | 24 +++++++++---------- .../FlakeyDriverIntegrationTests.cs | 8 +++---- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ZB.MOM.WW.OtOpcUa.Core/Resilience/CapabilityInvoker.cs b/src/ZB.MOM.WW.OtOpcUa.Core/Resilience/CapabilityInvoker.cs index e881dc7..3c06eb6 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Core/Resilience/CapabilityInvoker.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Core/Resilience/CapabilityInvoker.cs @@ -18,7 +18,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Resilience; public sealed class CapabilityInvoker { private readonly DriverResiliencePipelineBuilder _builder; - private readonly Guid _driverInstanceId; + private readonly string _driverInstanceId; private readonly Func _optionsAccessor; /// @@ -32,7 +32,7 @@ public sealed class CapabilityInvoker /// public CapabilityInvoker( DriverResiliencePipelineBuilder builder, - Guid driverInstanceId, + string driverInstanceId, Func optionsAccessor) { ArgumentNullException.ThrowIfNull(builder); diff --git a/src/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResiliencePipelineBuilder.cs b/src/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResiliencePipelineBuilder.cs index b3ad043..d7e25af 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResiliencePipelineBuilder.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResiliencePipelineBuilder.cs @@ -45,7 +45,7 @@ public sealed class DriverResiliencePipelineBuilder /// Which capability surface is being called. /// Per-driver-instance options (tier + per-capability overrides). public ResiliencePipeline GetOrCreate( - Guid driverInstanceId, + string driverInstanceId, string hostName, DriverCapability capability, DriverResilienceOptions options) @@ -59,7 +59,7 @@ public sealed class DriverResiliencePipelineBuilder } /// Drop cached pipelines for one driver instance (e.g. on ResilienceConfig change). Test + Admin-reload use. - public int Invalidate(Guid driverInstanceId) + public int Invalidate(string driverInstanceId) { var removed = 0; foreach (var key in _pipelines.Keys) @@ -114,5 +114,5 @@ public sealed class DriverResiliencePipelineBuilder return builder.Build(); } - private readonly record struct PipelineKey(Guid DriverInstanceId, string HostName, DriverCapability Capability); + private readonly record struct PipelineKey(string DriverInstanceId, string HostName, DriverCapability Capability); } diff --git a/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/CapabilityInvokerTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/CapabilityInvokerTests.cs index aa82652..9085524 100644 --- a/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/CapabilityInvokerTests.cs +++ b/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/CapabilityInvokerTests.cs @@ -11,7 +11,7 @@ public sealed class CapabilityInvokerTests private static CapabilityInvoker MakeInvoker( DriverResiliencePipelineBuilder builder, DriverResilienceOptions options) => - new(builder, Guid.NewGuid(), () => options); + new(builder, "drv-test", () => options); [Fact] public async Task Read_ReturnsValue_FromCallSite() diff --git a/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/DriverResiliencePipelineBuilderTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/DriverResiliencePipelineBuilderTests.cs index 67a3d1d..1167c5b 100644 --- a/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/DriverResiliencePipelineBuilderTests.cs +++ b/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/DriverResiliencePipelineBuilderTests.cs @@ -16,7 +16,7 @@ public sealed class DriverResiliencePipelineBuilderTests public async Task Read_Retries_Transient_Failures() { var builder = new DriverResiliencePipelineBuilder(); - var pipeline = builder.GetOrCreate(Guid.NewGuid(), "host-1", DriverCapability.Read, TierAOptions); + var pipeline = builder.GetOrCreate("drv-test", "host-1", DriverCapability.Read, TierAOptions); var attempts = 0; await pipeline.ExecuteAsync(async _ => @@ -33,7 +33,7 @@ public sealed class DriverResiliencePipelineBuilderTests public async Task Write_DoesNotRetry_OnFailure() { var builder = new DriverResiliencePipelineBuilder(); - var pipeline = builder.GetOrCreate(Guid.NewGuid(), "host-1", DriverCapability.Write, TierAOptions); + var pipeline = builder.GetOrCreate("drv-test", "host-1", DriverCapability.Write, TierAOptions); var attempts = 0; var ex = await Should.ThrowAsync(async () => @@ -54,7 +54,7 @@ public sealed class DriverResiliencePipelineBuilderTests public async Task AlarmAcknowledge_DoesNotRetry_OnFailure() { var builder = new DriverResiliencePipelineBuilder(); - var pipeline = builder.GetOrCreate(Guid.NewGuid(), "host-1", DriverCapability.AlarmAcknowledge, TierAOptions); + var pipeline = builder.GetOrCreate("drv-test", "host-1", DriverCapability.AlarmAcknowledge, TierAOptions); var attempts = 0; await Should.ThrowAsync(async () => @@ -74,7 +74,7 @@ public sealed class DriverResiliencePipelineBuilderTests public void Pipeline_IsIsolated_PerHost() { var builder = new DriverResiliencePipelineBuilder(); - var driverId = Guid.NewGuid(); + var driverId = "drv-test"; var hostA = builder.GetOrCreate(driverId, "host-a", DriverCapability.Read, TierAOptions); var hostB = builder.GetOrCreate(driverId, "host-b", DriverCapability.Read, TierAOptions); @@ -87,7 +87,7 @@ public sealed class DriverResiliencePipelineBuilderTests public void Pipeline_IsReused_ForSameTriple() { var builder = new DriverResiliencePipelineBuilder(); - var driverId = Guid.NewGuid(); + var driverId = "drv-test"; var first = builder.GetOrCreate(driverId, "host-a", DriverCapability.Read, TierAOptions); var second = builder.GetOrCreate(driverId, "host-a", DriverCapability.Read, TierAOptions); @@ -100,7 +100,7 @@ public sealed class DriverResiliencePipelineBuilderTests public void Pipeline_IsIsolated_PerCapability() { var builder = new DriverResiliencePipelineBuilder(); - var driverId = Guid.NewGuid(); + var driverId = "drv-test"; var read = builder.GetOrCreate(driverId, "host-a", DriverCapability.Read, TierAOptions); var write = builder.GetOrCreate(driverId, "host-a", DriverCapability.Write, TierAOptions); @@ -112,7 +112,7 @@ public sealed class DriverResiliencePipelineBuilderTests public async Task DeadHost_DoesNotOpenBreaker_ForSiblingHost() { var builder = new DriverResiliencePipelineBuilder(); - var driverId = Guid.NewGuid(); + var driverId = "drv-test"; var deadHost = builder.GetOrCreate(driverId, "dead-plc", DriverCapability.Read, TierAOptions); var liveHost = builder.GetOrCreate(driverId, "live-plc", DriverCapability.Read, TierAOptions); @@ -142,7 +142,7 @@ public sealed class DriverResiliencePipelineBuilderTests public async Task CircuitBreaker_Opens_AfterFailureThreshold_OnTierA() { var builder = new DriverResiliencePipelineBuilder(); - var pipeline = builder.GetOrCreate(Guid.NewGuid(), "host-1", DriverCapability.Write, TierAOptions); + var pipeline = builder.GetOrCreate("drv-test", "host-1", DriverCapability.Write, TierAOptions); var threshold = TierAOptions.Resolve(DriverCapability.Write).BreakerFailureThreshold; for (var i = 0; i < threshold; i++) @@ -174,7 +174,7 @@ public sealed class DriverResiliencePipelineBuilderTests }, }; var builder = new DriverResiliencePipelineBuilder(); - var pipeline = builder.GetOrCreate(Guid.NewGuid(), "host-1", DriverCapability.Read, tierAWithShortTimeout); + var pipeline = builder.GetOrCreate("drv-test", "host-1", DriverCapability.Read, tierAWithShortTimeout); await Should.ThrowAsync(async () => await pipeline.ExecuteAsync(async ct => @@ -187,8 +187,8 @@ public sealed class DriverResiliencePipelineBuilderTests public void Invalidate_Removes_OnlyMatchingInstance() { var builder = new DriverResiliencePipelineBuilder(); - var keepId = Guid.NewGuid(); - var dropId = Guid.NewGuid(); + var keepId = "drv-keep"; + var dropId = "drv-drop"; builder.GetOrCreate(keepId, "h", DriverCapability.Read, TierAOptions); builder.GetOrCreate(keepId, "h", DriverCapability.Write, TierAOptions); @@ -204,7 +204,7 @@ public sealed class DriverResiliencePipelineBuilderTests public async Task Cancellation_IsNot_Retried() { var builder = new DriverResiliencePipelineBuilder(); - var pipeline = builder.GetOrCreate(Guid.NewGuid(), "host-1", DriverCapability.Read, TierAOptions); + var pipeline = builder.GetOrCreate("drv-test", "host-1", DriverCapability.Read, TierAOptions); var attempts = 0; using var cts = new CancellationTokenSource(); cts.Cancel(); diff --git a/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/FlakeyDriverIntegrationTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/FlakeyDriverIntegrationTests.cs index 0622cdf..d58e393 100644 --- a/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/FlakeyDriverIntegrationTests.cs +++ b/tests/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/FlakeyDriverIntegrationTests.cs @@ -26,7 +26,7 @@ public sealed class FlakeyDriverIntegrationTests [DriverCapability.Read] = new(TimeoutSeconds: 2, RetryCount: 10, BreakerFailureThreshold: 50), }, }; - var invoker = new CapabilityInvoker(new DriverResiliencePipelineBuilder(), Guid.NewGuid(), () => options); + var invoker = new CapabilityInvoker(new DriverResiliencePipelineBuilder(), "drv-test", () => options); var result = await invoker.ExecuteAsync( DriverCapability.Read, @@ -50,7 +50,7 @@ public sealed class FlakeyDriverIntegrationTests [DriverCapability.Write] = new(TimeoutSeconds: 2, RetryCount: 5, BreakerFailureThreshold: 50), }, }; - var invoker = new CapabilityInvoker(new DriverResiliencePipelineBuilder(), Guid.NewGuid(), () => optionsWithAggressiveRetry); + var invoker = new CapabilityInvoker(new DriverResiliencePipelineBuilder(), "drv-test", () => optionsWithAggressiveRetry); await Should.ThrowAsync(async () => await invoker.ExecuteWriteAsync( @@ -74,7 +74,7 @@ public sealed class FlakeyDriverIntegrationTests [DriverCapability.Write] = new(TimeoutSeconds: 2, RetryCount: 5, BreakerFailureThreshold: 50), }, }; - var invoker = new CapabilityInvoker(new DriverResiliencePipelineBuilder(), Guid.NewGuid(), () => optionsWithRetry); + var invoker = new CapabilityInvoker(new DriverResiliencePipelineBuilder(), "drv-test", () => optionsWithRetry); var results = await invoker.ExecuteWriteAsync( "host-1", @@ -92,7 +92,7 @@ public sealed class FlakeyDriverIntegrationTests var flaky = new FlakeyDriver(failReadsBeforeIndex: 0); var options = new DriverResilienceOptions { Tier = DriverTier.A }; var builder = new DriverResiliencePipelineBuilder(); - var invoker = new CapabilityInvoker(builder, Guid.NewGuid(), () => options); + var invoker = new CapabilityInvoker(builder, "drv-test", () => options); // host-dead: force many failures to exhaust retries + trip breaker var threshold = options.Resolve(DriverCapability.Read).BreakerFailureThreshold;