Phase 6.1 Stream A — switch pipeline keys from Guid to string to match IDriver.DriverInstanceId
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) <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Resilience;
|
|||||||
public sealed class CapabilityInvoker
|
public sealed class CapabilityInvoker
|
||||||
{
|
{
|
||||||
private readonly DriverResiliencePipelineBuilder _builder;
|
private readonly DriverResiliencePipelineBuilder _builder;
|
||||||
private readonly Guid _driverInstanceId;
|
private readonly string _driverInstanceId;
|
||||||
private readonly Func<DriverResilienceOptions> _optionsAccessor;
|
private readonly Func<DriverResilienceOptions> _optionsAccessor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -32,7 +32,7 @@ public sealed class CapabilityInvoker
|
|||||||
/// </param>
|
/// </param>
|
||||||
public CapabilityInvoker(
|
public CapabilityInvoker(
|
||||||
DriverResiliencePipelineBuilder builder,
|
DriverResiliencePipelineBuilder builder,
|
||||||
Guid driverInstanceId,
|
string driverInstanceId,
|
||||||
Func<DriverResilienceOptions> optionsAccessor)
|
Func<DriverResilienceOptions> optionsAccessor)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(builder);
|
ArgumentNullException.ThrowIfNull(builder);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public sealed class DriverResiliencePipelineBuilder
|
|||||||
/// <param name="capability">Which capability surface is being called.</param>
|
/// <param name="capability">Which capability surface is being called.</param>
|
||||||
/// <param name="options">Per-driver-instance options (tier + per-capability overrides).</param>
|
/// <param name="options">Per-driver-instance options (tier + per-capability overrides).</param>
|
||||||
public ResiliencePipeline GetOrCreate(
|
public ResiliencePipeline GetOrCreate(
|
||||||
Guid driverInstanceId,
|
string driverInstanceId,
|
||||||
string hostName,
|
string hostName,
|
||||||
DriverCapability capability,
|
DriverCapability capability,
|
||||||
DriverResilienceOptions options)
|
DriverResilienceOptions options)
|
||||||
@@ -59,7 +59,7 @@ public sealed class DriverResiliencePipelineBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Drop cached pipelines for one driver instance (e.g. on ResilienceConfig change). Test + Admin-reload use.</summary>
|
/// <summary>Drop cached pipelines for one driver instance (e.g. on ResilienceConfig change). Test + Admin-reload use.</summary>
|
||||||
public int Invalidate(Guid driverInstanceId)
|
public int Invalidate(string driverInstanceId)
|
||||||
{
|
{
|
||||||
var removed = 0;
|
var removed = 0;
|
||||||
foreach (var key in _pipelines.Keys)
|
foreach (var key in _pipelines.Keys)
|
||||||
@@ -114,5 +114,5 @@ public sealed class DriverResiliencePipelineBuilder
|
|||||||
return builder.Build();
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public sealed class CapabilityInvokerTests
|
|||||||
private static CapabilityInvoker MakeInvoker(
|
private static CapabilityInvoker MakeInvoker(
|
||||||
DriverResiliencePipelineBuilder builder,
|
DriverResiliencePipelineBuilder builder,
|
||||||
DriverResilienceOptions options) =>
|
DriverResilienceOptions options) =>
|
||||||
new(builder, Guid.NewGuid(), () => options);
|
new(builder, "drv-test", () => options);
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Read_ReturnsValue_FromCallSite()
|
public async Task Read_ReturnsValue_FromCallSite()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
|||||||
public async Task Read_Retries_Transient_Failures()
|
public async Task Read_Retries_Transient_Failures()
|
||||||
{
|
{
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
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;
|
var attempts = 0;
|
||||||
|
|
||||||
await pipeline.ExecuteAsync(async _ =>
|
await pipeline.ExecuteAsync(async _ =>
|
||||||
@@ -33,7 +33,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
|||||||
public async Task Write_DoesNotRetry_OnFailure()
|
public async Task Write_DoesNotRetry_OnFailure()
|
||||||
{
|
{
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
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 attempts = 0;
|
||||||
|
|
||||||
var ex = await Should.ThrowAsync<InvalidOperationException>(async () =>
|
var ex = await Should.ThrowAsync<InvalidOperationException>(async () =>
|
||||||
@@ -54,7 +54,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
|||||||
public async Task AlarmAcknowledge_DoesNotRetry_OnFailure()
|
public async Task AlarmAcknowledge_DoesNotRetry_OnFailure()
|
||||||
{
|
{
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
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;
|
var attempts = 0;
|
||||||
|
|
||||||
await Should.ThrowAsync<InvalidOperationException>(async () =>
|
await Should.ThrowAsync<InvalidOperationException>(async () =>
|
||||||
@@ -74,7 +74,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
|||||||
public void Pipeline_IsIsolated_PerHost()
|
public void Pipeline_IsIsolated_PerHost()
|
||||||
{
|
{
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
var builder = new DriverResiliencePipelineBuilder();
|
||||||
var driverId = Guid.NewGuid();
|
var driverId = "drv-test";
|
||||||
|
|
||||||
var hostA = builder.GetOrCreate(driverId, "host-a", DriverCapability.Read, TierAOptions);
|
var hostA = builder.GetOrCreate(driverId, "host-a", DriverCapability.Read, TierAOptions);
|
||||||
var hostB = builder.GetOrCreate(driverId, "host-b", 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()
|
public void Pipeline_IsReused_ForSameTriple()
|
||||||
{
|
{
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
var builder = new DriverResiliencePipelineBuilder();
|
||||||
var driverId = Guid.NewGuid();
|
var driverId = "drv-test";
|
||||||
|
|
||||||
var first = builder.GetOrCreate(driverId, "host-a", DriverCapability.Read, TierAOptions);
|
var first = builder.GetOrCreate(driverId, "host-a", DriverCapability.Read, TierAOptions);
|
||||||
var second = 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()
|
public void Pipeline_IsIsolated_PerCapability()
|
||||||
{
|
{
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
var builder = new DriverResiliencePipelineBuilder();
|
||||||
var driverId = Guid.NewGuid();
|
var driverId = "drv-test";
|
||||||
|
|
||||||
var read = builder.GetOrCreate(driverId, "host-a", DriverCapability.Read, TierAOptions);
|
var read = builder.GetOrCreate(driverId, "host-a", DriverCapability.Read, TierAOptions);
|
||||||
var write = builder.GetOrCreate(driverId, "host-a", DriverCapability.Write, 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()
|
public async Task DeadHost_DoesNotOpenBreaker_ForSiblingHost()
|
||||||
{
|
{
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
var builder = new DriverResiliencePipelineBuilder();
|
||||||
var driverId = Guid.NewGuid();
|
var driverId = "drv-test";
|
||||||
|
|
||||||
var deadHost = builder.GetOrCreate(driverId, "dead-plc", DriverCapability.Read, TierAOptions);
|
var deadHost = builder.GetOrCreate(driverId, "dead-plc", DriverCapability.Read, TierAOptions);
|
||||||
var liveHost = builder.GetOrCreate(driverId, "live-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()
|
public async Task CircuitBreaker_Opens_AfterFailureThreshold_OnTierA()
|
||||||
{
|
{
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
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;
|
var threshold = TierAOptions.Resolve(DriverCapability.Write).BreakerFailureThreshold;
|
||||||
for (var i = 0; i < threshold; i++)
|
for (var i = 0; i < threshold; i++)
|
||||||
@@ -174,7 +174,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
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<TimeoutRejectedException>(async () =>
|
await Should.ThrowAsync<TimeoutRejectedException>(async () =>
|
||||||
await pipeline.ExecuteAsync(async ct =>
|
await pipeline.ExecuteAsync(async ct =>
|
||||||
@@ -187,8 +187,8 @@ public sealed class DriverResiliencePipelineBuilderTests
|
|||||||
public void Invalidate_Removes_OnlyMatchingInstance()
|
public void Invalidate_Removes_OnlyMatchingInstance()
|
||||||
{
|
{
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
var builder = new DriverResiliencePipelineBuilder();
|
||||||
var keepId = Guid.NewGuid();
|
var keepId = "drv-keep";
|
||||||
var dropId = Guid.NewGuid();
|
var dropId = "drv-drop";
|
||||||
|
|
||||||
builder.GetOrCreate(keepId, "h", DriverCapability.Read, TierAOptions);
|
builder.GetOrCreate(keepId, "h", DriverCapability.Read, TierAOptions);
|
||||||
builder.GetOrCreate(keepId, "h", DriverCapability.Write, TierAOptions);
|
builder.GetOrCreate(keepId, "h", DriverCapability.Write, TierAOptions);
|
||||||
@@ -204,7 +204,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
|||||||
public async Task Cancellation_IsNot_Retried()
|
public async Task Cancellation_IsNot_Retried()
|
||||||
{
|
{
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
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;
|
var attempts = 0;
|
||||||
using var cts = new CancellationTokenSource();
|
using var cts = new CancellationTokenSource();
|
||||||
cts.Cancel();
|
cts.Cancel();
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public sealed class FlakeyDriverIntegrationTests
|
|||||||
[DriverCapability.Read] = new(TimeoutSeconds: 2, RetryCount: 10, BreakerFailureThreshold: 50),
|
[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(
|
var result = await invoker.ExecuteAsync(
|
||||||
DriverCapability.Read,
|
DriverCapability.Read,
|
||||||
@@ -50,7 +50,7 @@ public sealed class FlakeyDriverIntegrationTests
|
|||||||
[DriverCapability.Write] = new(TimeoutSeconds: 2, RetryCount: 5, BreakerFailureThreshold: 50),
|
[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<InvalidOperationException>(async () =>
|
await Should.ThrowAsync<InvalidOperationException>(async () =>
|
||||||
await invoker.ExecuteWriteAsync(
|
await invoker.ExecuteWriteAsync(
|
||||||
@@ -74,7 +74,7 @@ public sealed class FlakeyDriverIntegrationTests
|
|||||||
[DriverCapability.Write] = new(TimeoutSeconds: 2, RetryCount: 5, BreakerFailureThreshold: 50),
|
[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(
|
var results = await invoker.ExecuteWriteAsync(
|
||||||
"host-1",
|
"host-1",
|
||||||
@@ -92,7 +92,7 @@ public sealed class FlakeyDriverIntegrationTests
|
|||||||
var flaky = new FlakeyDriver(failReadsBeforeIndex: 0);
|
var flaky = new FlakeyDriver(failReadsBeforeIndex: 0);
|
||||||
var options = new DriverResilienceOptions { Tier = DriverTier.A };
|
var options = new DriverResilienceOptions { Tier = DriverTier.A };
|
||||||
var builder = new DriverResiliencePipelineBuilder();
|
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
|
// host-dead: force many failures to exhaust retries + trip breaker
|
||||||
var threshold = options.Resolve(DriverCapability.Read).BreakerFailureThreshold;
|
var threshold = options.Resolve(DriverCapability.Read).BreakerFailureThreshold;
|
||||||
|
|||||||
Reference in New Issue
Block a user