docs+test(r2-02): bulkhead doc sweep + parsed-knob-must-be-wired guard (01/U-6, OVERALL theme 1)
This commit is contained in:
@@ -35,8 +35,6 @@ public sealed class DriverInstance
|
||||
/// Null = use the driver's tier defaults. When populated, expected shape:
|
||||
/// <code>
|
||||
/// {
|
||||
/// "bulkheadMaxConcurrent": 16,
|
||||
/// "bulkheadMaxQueue": 64,
|
||||
/// "capabilityPolicies": {
|
||||
/// "Read": { "timeoutSeconds": 5, "retryCount": 5, "breakerFailureThreshold": 3 },
|
||||
/// "Write": { "timeoutSeconds": 5, "retryCount": 0, "breakerFailureThreshold": 5 }
|
||||
|
||||
@@ -26,7 +26,9 @@ public sealed class DriverInstanceResilienceStatus
|
||||
/// <summary>Rolling count of consecutive Polly pipeline failures for this (instance, host).</summary>
|
||||
public int ConsecutiveFailures { get; set; }
|
||||
|
||||
/// <summary>Current Polly bulkhead depth (in-flight calls) for this (instance, host).</summary>
|
||||
/// <summary>In-flight capability-call depth for this (instance, host). Formerly fed a Polly
|
||||
/// concurrency-limiter strategy that was removed (R2-02/01/U-6); the column name is retained to avoid a
|
||||
/// migration on a reader-less entity — it now carries the tracker's in-flight-call count.</summary>
|
||||
public int CurrentBulkheadDepth { get; set; }
|
||||
|
||||
/// <summary>Most recent process recycle time (Tier C only; null for in-process tiers).</summary>
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction over the Phase 6.1 resilience pipeline that wraps a single driver instance's
|
||||
/// capability calls (retry / circuit-breaker / bulkhead / tracker telemetry). The Runtime
|
||||
/// capability calls (retry / circuit-breaker / in-flight accounting / tracker telemetry). The Runtime
|
||||
/// dispatch layer (<c>DriverInstanceActor</c>) consumes this interface instead of the concrete
|
||||
/// <c>CapabilityInvoker</c> so the actor assembly does NOT pull in
|
||||
/// <c>ZB.MOM.WW.OtOpcUa.Core</c> (which drags in Polly + driver hosting) — the same boundary
|
||||
@@ -15,7 +15,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// this interface without adapters. Callers pass the resolved <c>hostName</c> per call (a
|
||||
/// multi-host driver resolves it from the tag reference via <see cref="IPerCallHostResolver"/>;
|
||||
/// a single-host driver passes its <see cref="IDriver.DriverInstanceId"/>) so per-host breaker
|
||||
/// isolation + bulkhead-depth accounting key on the right target.
|
||||
/// isolation + in-flight-depth accounting key on the right target.
|
||||
/// </remarks>
|
||||
public interface IDriverCapabilityInvoker
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ public interface IPerCallHostResolver
|
||||
/// <summary>
|
||||
/// Resolve the host name for the given driver-side full reference. Returned value is
|
||||
/// used as the <c>hostName</c> argument to the Phase 6.1 <c>CapabilityInvoker</c> so
|
||||
/// per-host breaker isolation + per-host bulkhead accounting both kick in.
|
||||
/// per-host breaker isolation + per-host in-flight accounting both kick in.
|
||||
/// </summary>
|
||||
/// <param name="fullReference">The full reference of the tag or resource.</param>
|
||||
/// <returns>The host name responsible for serving the reference.</returns>
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class AlarmSurfaceInvoker
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to alarm events for a set of source node ids, fanning out by resolved host
|
||||
/// so per-host breakers / bulkheads apply. Returns one handle per host — callers that
|
||||
/// so per-host breakers apply. Returns one handle per host — callers that
|
||||
/// don't care about per-host separation may concatenate them. Each returned handle wraps
|
||||
/// the driver's opaque handle together with its resolved host so <see cref="UnsubscribeAsync"/>
|
||||
/// routes through the same host's pipeline that the subscription was created on.
|
||||
|
||||
@@ -40,7 +40,7 @@ public sealed class CapabilityInvoker : IDriverCapabilityInvoker
|
||||
/// pipeline-invalidate can take effect without restarting the invoker.
|
||||
/// </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 bulkhead-depth proxy.</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>
|
||||
public CapabilityInvoker(
|
||||
DriverResiliencePipelineBuilder builder,
|
||||
string driverInstanceId,
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Resilience;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per-device isolation. Composition from outside-in:
|
||||
/// <b>Timeout → Retry (when capability permits) → Circuit Breaker (when tier permits) → Bulkhead</b>.
|
||||
/// <b>Timeout → Retry (when capability permits) → Circuit Breaker (when tier permits)</b>.
|
||||
///
|
||||
/// <para>Pipeline resolution is lock-free on the hot path: the inner
|
||||
/// <see cref="ConcurrentDictionary{TKey,TValue}"/> caches a <see cref="ResiliencePipeline"/> per key;
|
||||
@@ -39,7 +39,7 @@ public sealed class DriverResiliencePipelineBuilder
|
||||
/// <param name="statusTracker">When non-null, every built pipeline wires Polly telemetry into
|
||||
/// the tracker — retries increment <c>ConsecutiveFailures</c>, breaker-open stamps
|
||||
/// <c>LastBreakerOpenUtc</c>, breaker-close resets failures. Feeds Admin <c>/hosts</c> +
|
||||
/// the Polly bulkhead-depth column. Absent tracker means no telemetry (unit tests +
|
||||
/// the in-flight-call-depth column. Absent tracker means no telemetry (unit tests +
|
||||
/// deployments that don't care about resilience observability).</param>
|
||||
/// <param name="logger">When non-null, retry / circuit-breaker-open / breaker-close events are
|
||||
/// logged (instance + host + capability + attempt/exception). This is the operator-facing
|
||||
|
||||
@@ -101,7 +101,7 @@ public sealed class DriverResilienceStatusTracker
|
||||
/// <summary>
|
||||
/// Record the entry of a capability call for this (instance, host). Increments the
|
||||
/// in-flight counter used as the <see cref="ResilienceStatusSnapshot.CurrentInFlight"/>
|
||||
/// surface (a cheap stand-in for Polly bulkhead depth). Paired with
|
||||
/// surface (in-flight capability-call depth). Paired with
|
||||
/// <see cref="RecordCallComplete"/>; callers use try/finally.
|
||||
/// </summary>
|
||||
/// <param name="driverInstanceId">The driver instance identifier.</param>
|
||||
@@ -159,7 +159,7 @@ public sealed record ResilienceStatusSnapshot
|
||||
/// <summary>
|
||||
/// In-flight capability calls against this (instance, host). Bumped on call entry +
|
||||
/// decremented on completion. Feeds <c>DriverInstanceResilienceStatus.CurrentBulkheadDepth</c>
|
||||
/// for Admin <c>/hosts</c> — a cheap proxy for the Polly bulkhead depth until the full
|
||||
/// for Admin <c>/hosts</c> — in-flight capability-call depth until the full
|
||||
/// telemetry observer lands.
|
||||
/// </summary>
|
||||
public int CurrentInFlight { get; init; }
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
||||
/// <para>Wire layer is libplctag 1.6.x. Per-device host addresses use
|
||||
/// the <c>ab://gateway[:port]/cip-path</c> canonical form parsed via
|
||||
/// <see cref="AbCipHostAddress.TryParse"/>; those strings become the <c>hostName</c> key
|
||||
/// for Polly bulkhead + circuit-breaker isolation.</para>
|
||||
/// for Polly per-host circuit-breaker isolation.</para>
|
||||
///
|
||||
/// <para>Tier A — in-process, shares server lifetime, no
|
||||
/// sidecar. <see cref="ReinitializeAsync"/> is the Tier-B escape hatch for recovering
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
||||
/// <summary>
|
||||
/// Parsed <c>ab://gateway[:port]/cip-path</c> host-address string used by the AbCip driver
|
||||
/// as the <c>hostName</c> key across <see cref="Core.Abstractions.IHostConnectivityProbe"/>,
|
||||
/// <see cref="Core.Abstractions.IPerCallHostResolver"/>, and the Polly bulkhead key
|
||||
/// <see cref="Core.Abstractions.IPerCallHostResolver"/>, and the Polly per-host pipeline key
|
||||
/// <c>(DriverInstanceId, hostName)</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
|
||||
|
||||
/// <summary>
|
||||
/// FOCAS driver configuration. One instance supports N CNC devices. Each device gets its own
|
||||
/// <c>(DriverInstanceId, HostAddress)</c> bulkhead key at the Phase 6.1 resilience layer.
|
||||
/// <c>(DriverInstanceId, HostAddress)</c> per-host pipeline key at the Phase 6.1 resilience layer.
|
||||
/// </summary>
|
||||
public sealed class FocasDriverOptions
|
||||
{
|
||||
|
||||
@@ -1627,7 +1627,7 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
else
|
||||
{
|
||||
// Attach the per-instance Phase 6.1 resilience invoker so this driver's capability calls
|
||||
// route through the retry/breaker/bulkhead/telemetry pipeline. The tier defaults are layered
|
||||
// route through the retry/breaker/telemetry pipeline. The tier defaults are layered
|
||||
// with the per-instance ResilienceConfig from the artifact; the pass-through factory yields a
|
||||
// no-op invoker on nodes without the real resilience factory bound.
|
||||
var invoker = _invokerFactory.Create(spec.DriverInstanceId, spec.DriverType, spec.ResilienceConfig);
|
||||
|
||||
@@ -139,7 +139,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
private readonly IDriver _driver;
|
||||
|
||||
/// <summary>Phase 6.1 resilience seam: every guarded driver-capability call this actor makes is
|
||||
/// routed through this invoker (retry / breaker / bulkhead / tracker telemetry). Defaults to
|
||||
/// routed through this invoker (retry / breaker / in-flight accounting / tracker telemetry). Defaults to
|
||||
/// <see cref="NullDriverCapabilityInvoker"/> (a genuine pass-through) when none is supplied — so
|
||||
/// unit tests + the F7 skeleton path behave exactly as a raw driver call. The fused Host builds a
|
||||
/// real per-instance invoker via <see cref="IDriverCapabilityInvokerFactory"/> and injects it at
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace ZB.MOM.WW.OtOpcUa.Analyzers;
|
||||
/// Diagnostic analyzer that flags direct invocations of Phase 6.1-wrapped driver-capability
|
||||
/// methods when the call is NOT already running inside a <c>CapabilityInvoker.ExecuteAsync</c>
|
||||
/// or <c>CapabilityInvoker.ExecuteWriteAsync</c> lambda. The wrapping is what gives us
|
||||
/// per-host breaker isolation, retry semantics, bulkhead-depth accounting, and alarm-ack
|
||||
/// per-host breaker isolation, retry semantics, in-flight-depth accounting, and alarm-ack
|
||||
/// idempotence guards — raw calls bypass all of that.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
@@ -102,7 +102,7 @@ public sealed class UnwrappedCapabilityCallAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor Rule = new(
|
||||
id: DiagnosticId,
|
||||
title: "Driver capability call must be wrapped in CapabilityInvoker",
|
||||
messageFormat: "Call to '{0}' is not wrapped in CapabilityInvoker.ExecuteAsync / ExecuteWriteAsync. Without the wrapping, Phase 6.1 resilience (retry, breaker, bulkhead, tracker telemetry) is bypassed for this call.",
|
||||
messageFormat: "Call to '{0}' is not wrapped in CapabilityInvoker.ExecuteAsync / ExecuteWriteAsync. Without the wrapping, Phase 6.1 resilience (retry, breaker, tracker telemetry) is bypassed for this call.",
|
||||
category: "OtOpcUa.Resilience",
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true,
|
||||
|
||||
Reference in New Issue
Block a user