fix(galaxy): #875 Galaxy initialize gets a 20 s budget via per-driver-type capability defaults
Gate F (#761) against the VD03 gateway: Galaxy initialize (session open + platform seed) took 4–5 s against Tier A's 5 s Initialize timeout, so 10 of 40 restarts failed, paid a 10 s retry and left a killed gateway worker. DriverFactoryRegistry.Register takes optional capabilityDefaults per driver type (GetCapabilityDefaults); DriverResilienceOptionsParser layers them over the tier table and under the per-instance ResilienceConfig; the Host wires the registry into DriverCapabilityInvokerFactory. Galaxy registers Initialize = 20 s (no retry, breaker 2), under the actor's 30 s pessimistic bound. Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Resilience;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Core.Hosting;
|
||||
@@ -21,6 +22,8 @@ public sealed class DriverFactoryRegistry
|
||||
= new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, DriverTier> _tiers
|
||||
= new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, IReadOnlyDictionary<DriverCapability, CapabilityPolicy>> _capabilityDefaults
|
||||
= new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -42,8 +45,14 @@ public sealed class DriverFactoryRegistry
|
||||
/// <c>DriverInstanceBootstrapper</c> can skip in-process drivers by tier check
|
||||
/// rather than by driver-type allow-list.
|
||||
/// </param>
|
||||
/// <param name="capabilityDefaults">
|
||||
/// Optional per-driver-TYPE capability policies layered over the tier defaults (and under any
|
||||
/// per-instance <c>ResilienceConfig</c>) — for a driver whose real cost of one capability does not fit its
|
||||
/// tier's table, e.g. Galaxy's initialize (session open + platform seed) against a real gateway (#875).
|
||||
/// </param>
|
||||
public void Register(string driverType, Func<string, string, IDriver> factory,
|
||||
DriverTier tier = DriverTier.A)
|
||||
DriverTier tier = DriverTier.A,
|
||||
IReadOnlyDictionary<DriverCapability, CapabilityPolicy>? capabilityDefaults = null)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(driverType);
|
||||
ArgumentNullException.ThrowIfNull(factory);
|
||||
@@ -54,6 +63,7 @@ public sealed class DriverFactoryRegistry
|
||||
$"DriverType '{driverType}' factory already registered for this process");
|
||||
_factories[driverType] = factory;
|
||||
_tiers[driverType] = tier;
|
||||
if (capabilityDefaults is { Count: > 0 }) _capabilityDefaults[driverType] = capabilityDefaults;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +93,15 @@ public sealed class DriverFactoryRegistry
|
||||
lock (_lock) return _tiers.GetValueOrDefault(driverType, DriverTier.A);
|
||||
}
|
||||
|
||||
/// <summary>The per-driver-type capability policy defaults registered with the factory, or null (#875).</summary>
|
||||
/// <param name="driverType">The driver type to look up.</param>
|
||||
/// <returns>The registered defaults, or <see langword="null"/> when the type registered none.</returns>
|
||||
public IReadOnlyDictionary<DriverCapability, CapabilityPolicy>? GetCapabilityDefaults(string driverType)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(driverType);
|
||||
lock (_lock) return _capabilityDefaults.GetValueOrDefault(driverType);
|
||||
}
|
||||
|
||||
/// <summary>Gets the collection of registered driver type names.</summary>
|
||||
public IReadOnlyCollection<string> RegisteredTypes
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@ public sealed class DriverCapabilityInvokerFactory : IDriverCapabilityInvokerFac
|
||||
private readonly Func<string, DriverTier> _tierResolver;
|
||||
private readonly ILogger? _logger;
|
||||
private readonly TimeSpan? _writeTimeoutCeiling;
|
||||
private readonly Func<string, IReadOnlyDictionary<DriverCapability, CapabilityPolicy>?>? _typeDefaultsResolver;
|
||||
private long _generation;
|
||||
|
||||
/// <summary>Construct the factory over the shared resilience infrastructure.</summary>
|
||||
@@ -46,12 +47,15 @@ public sealed class DriverCapabilityInvokerFactory : IDriverCapabilityInvokerFac
|
||||
/// cancels every write before the Write policy's own timeout can (lmxopcua#777). When non-null, a driver whose
|
||||
/// effective Write timeout exceeds it is logged as a warning at <see cref="Create"/> time: that policy timeout
|
||||
/// never takes effect. Null skips the check.</param>
|
||||
/// <param name="typeDefaultsResolver">Resolves a driver type's registered capability defaults
|
||||
/// (<c>DriverFactoryRegistry.GetCapabilityDefaults</c>, #875), layered over the tier table. Null = tier only.</param>
|
||||
public DriverCapabilityInvokerFactory(
|
||||
DriverResiliencePipelineBuilder builder,
|
||||
DriverResilienceStatusTracker statusTracker,
|
||||
Func<string, DriverTier> tierResolver,
|
||||
ILogger? logger = null,
|
||||
TimeSpan? writeTimeoutCeiling = null)
|
||||
TimeSpan? writeTimeoutCeiling = null,
|
||||
Func<string, IReadOnlyDictionary<DriverCapability, CapabilityPolicy>?>? typeDefaultsResolver = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
ArgumentNullException.ThrowIfNull(statusTracker);
|
||||
@@ -61,13 +65,15 @@ public sealed class DriverCapabilityInvokerFactory : IDriverCapabilityInvokerFac
|
||||
_tierResolver = tierResolver;
|
||||
_logger = logger;
|
||||
_writeTimeoutCeiling = writeTimeoutCeiling;
|
||||
_typeDefaultsResolver = typeDefaultsResolver;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDriverCapabilityInvoker Create(string driverInstanceId, string driverType, string? resilienceConfigJson = null)
|
||||
{
|
||||
var tier = _tierResolver(driverType);
|
||||
var options = DriverResilienceOptionsParser.ParseOrDefaults(tier, resilienceConfigJson, out var diagnostic);
|
||||
var options = DriverResilienceOptionsParser.ParseOrDefaults(
|
||||
tier, resilienceConfigJson, _typeDefaultsResolver?.Invoke(driverType), out var diagnostic);
|
||||
if (diagnostic is not null)
|
||||
{
|
||||
_logger?.LogWarning(
|
||||
|
||||
@@ -52,10 +52,32 @@ public static class DriverResilienceOptionsParser
|
||||
public static DriverResilienceOptions ParseOrDefaults(
|
||||
DriverTier tier,
|
||||
string? resilienceConfigJson,
|
||||
out string? parseDiagnostic) => ParseOrDefaults(tier, resilienceConfigJson, typeDefaults: null, out parseDiagnostic);
|
||||
|
||||
/// <summary>
|
||||
/// As <see cref="ParseOrDefaults(DriverTier, string?, out string?)"/>, with the driver TYPE's registered
|
||||
/// capability defaults (<c>DriverFactoryRegistry.GetCapabilityDefaults</c>, #875) layered over the tier
|
||||
/// table before the per-instance JSON is applied.
|
||||
/// </summary>
|
||||
/// <param name="tier">The driver's stability tier.</param>
|
||||
/// <param name="resilienceConfigJson">The per-instance ResilienceConfig JSON, or null.</param>
|
||||
/// <param name="typeDefaults">The driver type's capability defaults, or null.</param>
|
||||
/// <param name="parseDiagnostic">A diagnostic when the JSON was malformed or partly ignored.</param>
|
||||
/// <returns>The effective options.</returns>
|
||||
public static DriverResilienceOptions ParseOrDefaults(
|
||||
DriverTier tier,
|
||||
string? resilienceConfigJson,
|
||||
IReadOnlyDictionary<DriverCapability, CapabilityPolicy>? typeDefaults,
|
||||
out string? parseDiagnostic)
|
||||
{
|
||||
parseDiagnostic = null;
|
||||
var baseDefaults = DriverResilienceOptions.GetTierDefaults(tier);
|
||||
IReadOnlyDictionary<DriverCapability, CapabilityPolicy> baseDefaults = DriverResilienceOptions.GetTierDefaults(tier);
|
||||
if (typeDefaults is { Count: > 0 })
|
||||
{
|
||||
var layered = new Dictionary<DriverCapability, CapabilityPolicy>(baseDefaults);
|
||||
foreach (var (capability, policy) in typeDefaults) layered[capability] = policy;
|
||||
baseDefaults = layered;
|
||||
}
|
||||
var baseOptions = new DriverResilienceOptions { Tier = tier, CapabilityPolicies = baseDefaults };
|
||||
|
||||
if (string.IsNullOrWhiteSpace(resilienceConfigJson))
|
||||
|
||||
Reference in New Issue
Block a user