9cff87fe85
Remove project bookkeeping citations from shipped code comments across the solution: hyphenated task IDs (WP-14, StoreAndForward-025), milestone/task/ issue refs (M3, Task 4, Audit Log #23, #21), Bundle X task-bundle labels, and C/D/K/S/T phase labels. Comment text only — no code logic, string/log literals, or XML-doc structure changed. Genuine descriptions are preserved (only the citation is stripped), and technical lookalikes are retained (UTF-8, SHA-256, T00:00:00, M365, UTC-5, pre-C4/pre-C5 schema versions). Flagged by the new CommentChecker TaskReferenceInComment / TrackingReferenceInComment checks plus targeted grep passes; full solution builds clean, append-only guard tests pass.
163 lines
7.3 KiB
C#
163 lines
7.3 KiB
C#
using Akka.Actor;
|
|
using Microsoft.CodeAnalysis.Scripting;
|
|
using Microsoft.Extensions.Logging;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
|
|
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors;
|
|
|
|
/// <summary>
|
|
/// Alarm Execution Actor -- short-lived child of Alarm Actor.
|
|
/// Same pattern as ScriptExecutionActor.
|
|
/// CAN call Instance.CallScript() (ask to sibling Script Actor).
|
|
/// Instance scripts CANNOT call alarm on-trigger scripts (no API for it).
|
|
/// Supervision: Stop on unhandled exception.
|
|
/// </summary>
|
|
public class AlarmExecutionActor : ReceiveActor
|
|
{
|
|
/// <summary>Initializes a new <see cref="AlarmExecutionActor"/> and immediately schedules execution of the alarm on-trigger script.</summary>
|
|
/// <param name="alarmName">The canonical name of the alarm that triggered.</param>
|
|
/// <param name="instanceName">The name of the owning instance.</param>
|
|
/// <param name="level">The alarm severity level at the time of triggering.</param>
|
|
/// <param name="priority">The alarm priority value.</param>
|
|
/// <param name="message">The alarm message to pass to the script.</param>
|
|
/// <param name="compiledScript">The pre-compiled on-trigger script to execute.</param>
|
|
/// <param name="instanceActor">Reference to the parent instance actor for attribute/script calls.</param>
|
|
/// <param name="sharedScriptLibrary">Shared script library providing common utilities.</param>
|
|
/// <param name="options">Site runtime configuration options, including the execution timeout.</param>
|
|
/// <param name="logger">Logger for execution diagnostics.</param>
|
|
/// <param name="executionTimeoutSeconds">The on-trigger script's per-script execution timeout in seconds. Null or non-positive falls back to the global <see cref="SiteRuntimeOptions.ScriptExecutionTimeoutSeconds"/>.</param>
|
|
/// <param name="parentExecutionId">
|
|
/// ParentExecutionId tag-cascade: the execution id of
|
|
/// the context that fired this alarm, threaded into the on-trigger script's
|
|
/// <see cref="ScriptRuntimeContext"/> as its <c>ParentExecutionId</c> so the
|
|
/// alarm-triggered run chains under its firing context. Null today (no
|
|
/// Guid-typed firing id exists yet) — the run is a root, but the plumbing
|
|
/// is in place for a future firing id.
|
|
/// </param>
|
|
public AlarmExecutionActor(
|
|
string alarmName,
|
|
string instanceName,
|
|
AlarmLevel level,
|
|
int priority,
|
|
string message,
|
|
Script<object?> compiledScript,
|
|
IActorRef instanceActor,
|
|
SharedScriptLibrary sharedScriptLibrary,
|
|
SiteRuntimeOptions options,
|
|
ILogger logger,
|
|
// Per-script execution timeout override (seconds) for the
|
|
// alarm on-trigger script. Null or non-positive falls back to the global.
|
|
int? executionTimeoutSeconds = null,
|
|
// The firing context's execution id (null today).
|
|
Guid? parentExecutionId = null)
|
|
{
|
|
var self = Self;
|
|
var parent = Context.Parent;
|
|
|
|
ExecuteAlarmScript(
|
|
alarmName, instanceName, level, priority, message,
|
|
compiledScript, instanceActor,
|
|
sharedScriptLibrary, options, self, parent, logger,
|
|
executionTimeoutSeconds, parentExecutionId);
|
|
}
|
|
|
|
private static void ExecuteAlarmScript(
|
|
string alarmName,
|
|
string instanceName,
|
|
AlarmLevel level,
|
|
int priority,
|
|
string message,
|
|
Script<object?> compiledScript,
|
|
IActorRef instanceActor,
|
|
SharedScriptLibrary sharedScriptLibrary,
|
|
SiteRuntimeOptions options,
|
|
IActorRef self,
|
|
IActorRef parent,
|
|
ILogger logger,
|
|
int? executionTimeoutSeconds,
|
|
Guid? parentExecutionId)
|
|
{
|
|
// Per-script timeout overrides the global default. A null or
|
|
// non-positive per-script value (≤ 0) falls back to the global.
|
|
var timeout = TimeSpan.FromSeconds(
|
|
executionTimeoutSeconds is { } perScript && perScript > 0
|
|
? perScript
|
|
: options.ScriptExecutionTimeoutSeconds);
|
|
|
|
// Run the alarm on-trigger body on the dedicated
|
|
// script-execution scheduler, not the shared .NET thread pool.
|
|
var scheduler = ScriptExecutionScheduler.Shared(options);
|
|
|
|
_ = Task.Factory.StartNew(async () =>
|
|
{
|
|
using var cts = new CancellationTokenSource(timeout);
|
|
try
|
|
{
|
|
// AlarmExecutionActor can call Instance.CallScript()
|
|
// via the ScriptRuntimeContext injected into globals
|
|
var context = new ScriptRuntimeContext(
|
|
instanceActor,
|
|
self,
|
|
sharedScriptLibrary,
|
|
currentCallDepth: 0,
|
|
options.MaxScriptCallDepth,
|
|
timeout,
|
|
instanceName,
|
|
logger,
|
|
// ParentExecutionId tag-cascade: the
|
|
// alarm on-trigger run mints its own fresh ExecutionId (the
|
|
// ctor's `?? NewGuid()` fallback) and records the firing
|
|
// context's id as its ParentExecutionId — null today, so the
|
|
// run is a root, but the plumbing exists for a future
|
|
// firing id.
|
|
parentExecutionId: parentExecutionId,
|
|
// WaitForAttribute (spec §4.4): thread the alarm on-trigger
|
|
// script's per-script execution-timeout token so a
|
|
// Attributes.WaitAsync inside an on-trigger script is bounded
|
|
// by the same script deadline.
|
|
scriptTimeoutToken: cts.Token);
|
|
|
|
var globals = new ScriptGlobals
|
|
{
|
|
Instance = context,
|
|
Parameters = new ScriptParameters(),
|
|
CancellationToken = cts.Token,
|
|
Alarm = new AlarmContext
|
|
{
|
|
Name = alarmName,
|
|
Level = level,
|
|
Priority = priority,
|
|
Message = message
|
|
}
|
|
};
|
|
|
|
await compiledScript.RunAsync(globals, cts.Token);
|
|
|
|
parent.Tell(new AlarmActor.AlarmExecutionCompleted(alarmName, true));
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
logger.LogWarning(
|
|
"Alarm on-trigger script for {Alarm} on {Instance} timed out",
|
|
alarmName, instanceName);
|
|
parent.Tell(new AlarmActor.AlarmExecutionCompleted(alarmName, false));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Failures logged, alarm continues
|
|
logger.LogError(ex,
|
|
"Alarm on-trigger script for {Alarm} on {Instance} failed",
|
|
alarmName, instanceName);
|
|
parent.Tell(new AlarmActor.AlarmExecutionCompleted(alarmName, false));
|
|
}
|
|
finally
|
|
{
|
|
self.Tell(PoisonPill.Instance);
|
|
}
|
|
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, scheduler).Unwrap();
|
|
}
|
|
}
|