fix(r2-02): options-generation in the pipeline cache key — stale respawn re-cache unreachable (01/S-7, 03/S13)
This commit is contained in:
+48
@@ -356,6 +356,54 @@ public sealed class DriverResiliencePipelineBuilderTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 01/S-7 = 03/S13: a stale-epoch re-cache must not be served to a new generation. Deterministic
|
||||
/// replay of the respawn race — the old child's late <c>GetOrCreate</c> re-caches under generation 1
|
||||
/// AFTER the invalidate; the new invoker (generation 2) with new options must build + serve a pipeline
|
||||
/// honoring the NEW options, not the poisoned old-generation entry. Generations make the interleaving
|
||||
/// expressible sequentially — no timing dependence.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task StaleGeneration_ReCache_IsNotServed_ToNewGeneration()
|
||||
{
|
||||
var builder = new DriverResiliencePipelineBuilder();
|
||||
var optsA = new DriverResilienceOptions
|
||||
{
|
||||
Tier = DriverTier.A,
|
||||
CapabilityPolicies = new Dictionary<DriverCapability, CapabilityPolicy>
|
||||
{
|
||||
[DriverCapability.Read] = new(TimeoutSeconds: 5, RetryCount: 3, BreakerFailureThreshold: 0),
|
||||
},
|
||||
};
|
||||
var optsB = new DriverResilienceOptions
|
||||
{
|
||||
Tier = DriverTier.A,
|
||||
CapabilityPolicies = new Dictionary<DriverCapability, CapabilityPolicy>
|
||||
{
|
||||
[DriverCapability.Read] = new(TimeoutSeconds: 5, RetryCount: 0, BreakerFailureThreshold: 0),
|
||||
},
|
||||
};
|
||||
|
||||
// gen 1 warms the cache with the retrying optsA.
|
||||
builder.GetOrCreate("i", "h", DriverCapability.Read, optsA, optionsGeneration: 1);
|
||||
builder.Invalidate("i");
|
||||
// The lingering old child's late call re-caches optsA under its OWN generation 1.
|
||||
builder.GetOrCreate("i", "h", DriverCapability.Read, optsA, optionsGeneration: 1);
|
||||
|
||||
// The new invoker (gen 2, optsB) must get a pipeline honoring optsB (no retry), not the poisoned one.
|
||||
var newPipeline = builder.GetOrCreate("i", "h", DriverCapability.Read, optsB, optionsGeneration: 2);
|
||||
var attempts = 0;
|
||||
await Should.ThrowAsync<InvalidOperationException>(async () =>
|
||||
await newPipeline.ExecuteAsync(async _ =>
|
||||
{
|
||||
attempts++;
|
||||
await Task.Yield();
|
||||
throw new InvalidOperationException("boom");
|
||||
}));
|
||||
|
||||
attempts.ShouldBe(1, "the new generation must honor optsB (retryCount 0), not the stale optsA re-cache");
|
||||
}
|
||||
|
||||
/// <summary>Minimal in-memory <see cref="ILogger"/> capturing (level, formatted-message) pairs so the
|
||||
/// resilience logging contract can be asserted without a real logging provider.</summary>
|
||||
private sealed class CapturingLogger : ILogger
|
||||
|
||||
Reference in New Issue
Block a user