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:
Joseph Doherty
2026-07-13 10:22:07 -04:00
parent 112f88c9fe
commit 5daf7155a8
4 changed files with 76 additions and 8 deletions
@@ -28,6 +28,7 @@ public sealed class CapabilityInvoker : IDriverCapabilityInvoker
private readonly string _driverType;
private readonly Func<DriverResilienceOptions> _optionsAccessor;
private readonly DriverResilienceStatusTracker? _statusTracker;
private readonly long _optionsGeneration;
private DriverResilienceOptions? _noRetryWriteOptions;
/// <summary>
@@ -41,12 +42,18 @@ public sealed class CapabilityInvoker : IDriverCapabilityInvoker
/// </param>
/// <param name="driverType">Driver type name for structured-log enrichment (e.g. <c>"Modbus"</c>).</param>
/// <param name="statusTracker">Optional resilience-status tracker. When wired, every capability call records start/complete so Admin <c>/hosts</c> can surface <see cref="ResilienceStatusSnapshot.CurrentInFlight"/> as the in-flight-call-depth proxy.</param>
/// <param name="optionsGeneration">
/// Options epoch for this invoker (01/S-7). Threaded into every pipeline-cache lookup so a lingering
/// old child's late re-cache can never poison a newer invoker's pipeline. Defaults to 0 so existing
/// callers/tests are unaffected.
/// </param>
public CapabilityInvoker(
DriverResiliencePipelineBuilder builder,
string driverInstanceId,
Func<DriverResilienceOptions> optionsAccessor,
string driverType = "Unknown",
DriverResilienceStatusTracker? statusTracker = null)
DriverResilienceStatusTracker? statusTracker = null,
long optionsGeneration = 0)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(optionsAccessor);
@@ -56,6 +63,7 @@ public sealed class CapabilityInvoker : IDriverCapabilityInvoker
_driverType = driverType;
_optionsAccessor = optionsAccessor;
_statusTracker = statusTracker;
_optionsGeneration = optionsGeneration;
}
/// <inheritdoc />
@@ -123,7 +131,7 @@ public sealed class CapabilityInvoker : IDriverCapabilityInvoker
// safe and removes the per-write allocation the previous once-per-call snapshot incurred. The
// null-check-assign tolerates a benign first-call race (both threads build an equivalent record).
var noRetryOptions = _noRetryWriteOptions ??= BuildNoRetryWriteOptions();
var pipeline = _builder.GetOrCreate(_driverInstanceId, $"{hostName}::non-idempotent", DriverCapability.Write, noRetryOptions);
var pipeline = _builder.GetOrCreate(_driverInstanceId, $"{hostName}::non-idempotent", DriverCapability.Write, noRetryOptions, _optionsGeneration);
// Record in-flight accounting on the CALLER's host (the "::non-idempotent" suffix is a
// pipeline-cache key, not an operator-facing host) so Admin /hosts sees write depth too.
_statusTracker?.RecordCallStart(_driverInstanceId, hostName);
@@ -156,5 +164,5 @@ public sealed class CapabilityInvoker : IDriverCapabilityInvoker
}
private ResiliencePipeline ResolvePipeline(DriverCapability capability, string hostName) =>
_builder.GetOrCreate(_driverInstanceId, hostName, capability, _optionsAccessor());
_builder.GetOrCreate(_driverInstanceId, hostName, capability, _optionsAccessor(), _optionsGeneration);
}