feat(scripting): bind the Scripting section node-wide and clamp its knobs
Program.cs binds ScriptingOptions from Scripting:* once, clamps every numeric knob through ScriptingOptions.Sanitized (logging a Warning per adjustment) and registers the one instance. It now reaches all three script seams: RoslynVirtualTagEvaluator, the scripted-alarm engine built in DriverHostActor (new trailing Props parameter, wired in WithOtOpcUaRuntimeActors) and every Calculation driver (node options as the base, per-driver config keys override one knob on a copy). ScriptRunGuard sanitizes again in its ctor and computes the stuck threshold with a saturating multiply, so an absurd StuckOrphanQuarantineFactor can no longer throw OverflowException on a refused run. Refs #731 #734 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -68,6 +68,8 @@ public static class DriverFactoryBootstrap
|
||||
// The calc driver needs the root script logger so a script failure fans out onto the
|
||||
// script-logs topic; resolve it here (null on nodes without the script pipeline wired).
|
||||
var scriptRoot = sp.GetService<ZB.MOM.WW.OtOpcUa.Core.Scripting.ScriptRootLogger>();
|
||||
// The node-wide Scripting section (bound + clamped in Program.cs); null in test graphs ⇒ defaults.
|
||||
var scripting = sp.GetService<ZB.MOM.WW.OtOpcUa.Core.Scripting.ScriptingOptions>();
|
||||
// The Galaxy driver resolves its Gateway.ApiKeySecretRef secret: arm through the
|
||||
// shared ISecretResolver (registered unconditionally by AddZbSecrets on every node).
|
||||
var secretResolver = sp.GetRequiredService<ISecretResolver>();
|
||||
@@ -84,7 +86,7 @@ public static class DriverFactoryBootstrap
|
||||
// failures; without this it writes to NullLogger in production.
|
||||
ConfigureAbTransportLogging(sp);
|
||||
Register(registry, loggerFactory, secretResolver, primaryRoleSource, ResolveAbCipDefaultTransport(sp),
|
||||
scriptRoot);
|
||||
scriptRoot, scripting);
|
||||
return registry;
|
||||
});
|
||||
services.AddSingleton<IDriverFactory>(sp =>
|
||||
@@ -209,11 +211,12 @@ public static class DriverFactoryBootstrap
|
||||
ISecretResolver secretResolver,
|
||||
IDriverPrimaryRoleSource primaryRoleSource,
|
||||
Driver.AbCip.AbCipTransport abCipDefaultTransport,
|
||||
ZB.MOM.WW.OtOpcUa.Core.Scripting.ScriptRootLogger? scriptRoot = null)
|
||||
ZB.MOM.WW.OtOpcUa.Core.Scripting.ScriptRootLogger? scriptRoot = null,
|
||||
ZB.MOM.WW.OtOpcUa.Core.Scripting.ScriptingOptions? scripting = null)
|
||||
{
|
||||
Driver.AbCip.AbCipDriverFactoryExtensions.Register(registry, loggerFactory, abCipDefaultTransport);
|
||||
Driver.AbLegacy.AbLegacyDriverFactoryExtensions.Register(registry, loggerFactory);
|
||||
Driver.Calculation.CalculationDriverFactoryExtensions.Register(registry, loggerFactory, scriptRoot);
|
||||
Driver.Calculation.CalculationDriverFactoryExtensions.Register(registry, loggerFactory, scriptRoot, scripting);
|
||||
Driver.FOCAS.FocasDriverFactoryExtensions.Register(registry, loggerFactory);
|
||||
Driver.Galaxy.GalaxyDriverFactoryExtensions.Register(registry, secretResolver, loggerFactory);
|
||||
Driver.Modbus.ModbusDriverFactoryExtensions.Register(registry, loggerFactory);
|
||||
|
||||
@@ -365,10 +365,22 @@ if (hasDriver)
|
||||
// F8b — production virtual-tag evaluator (Roslyn-compiled scripts cached per expression).
|
||||
// Replaces the F8-default NullVirtualTagEvaluator so VirtualTagActor evaluates real user
|
||||
// scripts at runtime.
|
||||
// Node-wide script run-guard knobs (Scripting:MaxAllocBytesPerRun / QuarantineAfterConsecutiveTimeouts /
|
||||
// StuckOrphanQuarantineFactor). Bound once and clamped into sane ranges here, so an absurd value is a startup
|
||||
// Warning rather than an OverflowException inside a script run. The one instance is shared by every script
|
||||
// seam on the node: the virtual-tag evaluator below, the scripted-alarm engine (DriverHostActor, via
|
||||
// WithOtOpcUaRuntimeActors) and every Calculation driver (DriverFactoryBootstrap).
|
||||
var scriptingOptions = (builder.Configuration.GetSection(ScriptingOptions.SectionName).Get<ScriptingOptions>()
|
||||
?? new ScriptingOptions()).Sanitized(out var scriptingAdjustments);
|
||||
foreach (var adjustment in scriptingAdjustments)
|
||||
Log.Warning("Scripting misconfiguration detected at startup: {Adjustment}", adjustment);
|
||||
builder.Services.AddSingleton(scriptingOptions);
|
||||
|
||||
builder.Services.AddSingleton<RoslynVirtualTagEvaluator>(sp =>
|
||||
new RoslynVirtualTagEvaluator(
|
||||
sp.GetRequiredService<ILoggerFactory>().CreateLogger<RoslynVirtualTagEvaluator>(),
|
||||
sp.GetRequiredService<ScriptRootLogger>()));
|
||||
sp.GetRequiredService<ScriptRootLogger>(),
|
||||
scripting: sp.GetRequiredService<ScriptingOptions>()));
|
||||
builder.Services.AddSingleton<IVirtualTagEvaluator>(sp => sp.GetRequiredService<RoslynVirtualTagEvaluator>());
|
||||
|
||||
// Script-log fan-out (Layer 0). The DPS publisher resolves the ActorSystem lazily so it never
|
||||
|
||||
@@ -202,6 +202,9 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
private readonly IHistoryWriter _historyWriter;
|
||||
private readonly IActorRef? _virtualTagHostOverride;
|
||||
private readonly ScriptRootLogger? _scriptRootLogger;
|
||||
|
||||
/// <summary>Node-wide script run-guard options for the scripted-alarm engine; null ⇒ compiled defaults.</summary>
|
||||
private readonly ScriptingOptions? _scripting;
|
||||
private readonly IActorRef? _scriptedAlarmHostOverride;
|
||||
private readonly ILoggingAdapter _log = Context.GetLogger();
|
||||
|
||||
@@ -694,6 +697,8 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
/// capture to a mesh-partner Primary the cluster failure detector reports unreachable.</param>
|
||||
/// <param name="historyUpdateRoleView">Where the OPC UA HistoryUpdate verdict is published for the node manager's
|
||||
/// HistoryUpdate Primary gate. Null on nodes that wire no redundancy manager.</param>
|
||||
/// <param name="scripting">The node-wide <c>Scripting</c> run-guard options handed to the scripted-alarm
|
||||
/// engine. Null ⇒ the compiled defaults.</param>
|
||||
/// <returns>The Akka.NET <see cref="Akka.Actor.Props"/> used to spawn this actor.</returns>
|
||||
public static Props Props(
|
||||
IDbContextFactory<OtOpcUaConfigDbContext>? dbFactory,
|
||||
@@ -728,7 +733,8 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
Func<CommonsNodeId, bool>? sharesAlarmQueueWithLocal = null,
|
||||
TimeSpan? applyOutcomeTimeout = null,
|
||||
bool captureWhilePrimaryUnreachable = true,
|
||||
IHistoryUpdateRoleView? historyUpdateRoleView = null) =>
|
||||
IHistoryUpdateRoleView? historyUpdateRoleView = null,
|
||||
ScriptingOptions? scripting = null) =>
|
||||
// WARNING: this forwarding list is POSITIONAL, and Props.Create compiles it into an
|
||||
// expression tree. Six IActorRef? parameters and several interface-typed ones mean a
|
||||
// mis-ordered argument is usually type-compatible and therefore compiles clean, then binds
|
||||
@@ -741,7 +747,8 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
deploymentArtifactCache, redundancyRoleView, replicationPeerHost, fetchAndCacheMode, artifactFetcher,
|
||||
alarmStateStore, telemetryHub, primaryRoleSource, historizationRoleView,
|
||||
sharesHistorizationScopeWithLocal, applyWedgeTimeout, reprovisionBackoff, commsRecoveryAuditDelay,
|
||||
sharesAlarmQueueWithLocal, applyOutcomeTimeout, captureWhilePrimaryUnreachable, historyUpdateRoleView));
|
||||
sharesAlarmQueueWithLocal, applyOutcomeTimeout, captureWhilePrimaryUnreachable, historyUpdateRoleView,
|
||||
scripting));
|
||||
|
||||
/// <summary>Initializes a new DriverHostActor with the specified dependencies.</summary>
|
||||
/// <param name="dbFactory">Database context factory for configuration database access.</param>
|
||||
@@ -830,6 +837,8 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
/// (<see cref="PrimaryGatePolicy.ShouldServeHistoryUpdate"/> — the data-plane verdict, but closed until the first
|
||||
/// redundancy snapshot) is published for the node manager's HistoryUpdate Primary gate. Null on nodes that wire no
|
||||
/// redundancy manager.</param>
|
||||
/// <param name="scripting">The node-wide <c>Scripting</c> run-guard options handed to the scripted-alarm
|
||||
/// engine. Null ⇒ the compiled defaults.</param>
|
||||
public DriverHostActor(
|
||||
IDbContextFactory<OtOpcUaConfigDbContext>? dbFactory,
|
||||
CommonsNodeId localNode,
|
||||
@@ -863,8 +872,10 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
Func<CommonsNodeId, bool>? sharesAlarmQueueWithLocal = null,
|
||||
TimeSpan? applyOutcomeTimeout = null,
|
||||
bool captureWhilePrimaryUnreachable = true,
|
||||
IHistoryUpdateRoleView? historyUpdateRoleView = null)
|
||||
IHistoryUpdateRoleView? historyUpdateRoleView = null,
|
||||
ScriptingOptions? scripting = null)
|
||||
{
|
||||
_scripting = scripting;
|
||||
_captureWhilePrimaryUnreachable = captureWhilePrimaryUnreachable;
|
||||
_historyUpdateRoleView = historyUpdateRoleView;
|
||||
_applyOutcomeTimeout = applyOutcomeTimeout ?? DefaultApplyOutcomeTimeout;
|
||||
@@ -1181,7 +1192,8 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
var store = _alarmStateStore;
|
||||
var upstream = new DependencyMuxTagUpstreamSource();
|
||||
var engine = new ScriptedAlarmEngine(
|
||||
upstream, store, new ScriptLoggerFactory(_scriptRootLogger.Logger), _scriptRootLogger.Logger);
|
||||
upstream, store, new ScriptLoggerFactory(_scriptRootLogger.Logger), _scriptRootLogger.Logger,
|
||||
scripting: _scripting);
|
||||
_scriptedAlarmHost = Context.ActorOf(
|
||||
ScriptedAlarmHostActor.Props(
|
||||
_opcUaPublishActor, _dependencyMux, upstream, engine, _localNode, telemetryHub: _telemetryHub),
|
||||
|
||||
@@ -638,7 +638,10 @@ public static class ServiceCollectionExtensions
|
||||
sharesAlarmQueueWithLocal: BuildAlarmQueuePeerPredicate(
|
||||
resolver.GetService<IConfiguration>(), roleInfo, replicationPeerHost),
|
||||
// The HistoryUpdate verdict (closed until the first redundancy snapshot).
|
||||
historyUpdateRoleView: resolver.GetService<IHistoryUpdateRoleView>()),
|
||||
historyUpdateRoleView: resolver.GetService<IHistoryUpdateRoleView>(),
|
||||
// The node-wide Scripting section (bound + clamped in Program.cs) for the scripted-alarm
|
||||
// engine. Unbound in test graphs ⇒ null ⇒ the compiled defaults.
|
||||
scripting: resolver.GetService<ScriptingOptions>()),
|
||||
DriverHostActorName);
|
||||
registry.Register<DriverHostActorKey>(driverHost);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user