docs(src): fix CommentChecker findings across src/ — 1,032 → 5 (all false positives)
Comment/XML-doc changes only, no code logic: - strip project-tracking references (WP*, P*-*, row-NN, #NN, Task N) from comments, keeping the explanatory rationale - add missing summaries, <param>, <returns>, <typeparam> and <inheritdoc/> - replace misused <inheritdoc/> with real docs; where <inheritdoc/> was combined with useful remarks, move the rationale into // comments - drop stale replicationActor <param> on DeploymentManagerActor ctor Remaining 5 are false positives (CWE-1236 x2, failures-1, year-0001, M365). Build 0 errors / 0 warnings.
This commit is contained in:
@@ -42,34 +42,34 @@ public class AlarmActor : ReceiveActor
|
||||
private readonly IServiceProvider? _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Script-execution scheduler seam (#18): the process-wide
|
||||
/// Script-execution scheduler seam: the process-wide
|
||||
/// <see cref="ScriptExecutionScheduler"/> when null, or an injected instance so this
|
||||
/// alarm's launched on-trigger scripts run on a caller-owned pool. Resolved lazily at
|
||||
/// each use so the null (host) path is unchanged.
|
||||
///
|
||||
/// <para>WP3.1: trigger-expression evaluation no longer uses this scheduler — see
|
||||
/// <see cref="_evalGate"/>. That split is the whole point of finding #4: an alarm whose
|
||||
/// <para>Trigger-expression evaluation no longer uses this scheduler — see
|
||||
/// <see cref="_evalGate"/>. That split is the whole point: an alarm whose
|
||||
/// Expression trigger should raise in milliseconds must not queue behind blocking
|
||||
/// script bodies.</para>
|
||||
/// </summary>
|
||||
private readonly ScriptExecutionScheduler? _scheduler;
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1 (finding #4): the concurrency gate for trigger-expression evaluation, or null
|
||||
/// The concurrency gate for trigger-expression evaluation, or null
|
||||
/// for the process-wide <see cref="TriggerEvalGate.Shared"/>. Evaluations run as plain
|
||||
/// async work on the shared .NET thread pool behind this gate.
|
||||
/// </summary>
|
||||
private readonly TriggerEvalGate? _evalGate;
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1: on-trigger runs launched but not yet completed. Incremented at launch,
|
||||
/// On-trigger runs launched but not yet completed. Incremented at launch,
|
||||
/// decremented on <see cref="AlarmExecutionCompleted"/>, which every terminal path
|
||||
/// emits — including the launch-path catch. Touched only on the actor thread.
|
||||
/// </summary>
|
||||
private int _runsInFlight;
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1: when the last shed Warning site event was emitted for this alarm. Sheds are
|
||||
/// When the last shed Warning site event was emitted for this alarm. Sheds are
|
||||
/// always counted; the event is rate-limited to one per alarm per minute.
|
||||
/// </summary>
|
||||
private DateTimeOffset _lastShedEventUtc = DateTimeOffset.MinValue;
|
||||
@@ -130,8 +130,7 @@ public class AlarmActor : ReceiveActor
|
||||
private bool _evalPending;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 (ParentExecutionId tag-cascade): the
|
||||
/// <see cref="AttributeValueChanged.SourceExecutionId"/> of the most recent
|
||||
/// The <see cref="AttributeValueChanged.SourceExecutionId"/> of the most recent
|
||||
/// change folded into <see cref="_attributeSnapshot"/>. Expression triggers
|
||||
/// evaluate a whole snapshot off the dispatcher, so there is no single
|
||||
/// "triggering change" in scope when the boolean result comes back — this
|
||||
@@ -150,8 +149,7 @@ public class AlarmActor : ReceiveActor
|
||||
internal IReadOnlyDictionary<string, object?>? SeedAttributesReference { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 (ParentExecutionId tag-cascade): the
|
||||
/// <c>parentExecutionId</c> handed to the most recently spawned
|
||||
/// The <c>parentExecutionId</c> handed to the most recently spawned
|
||||
/// on-trigger run — i.e. the execution whose attribute
|
||||
/// write fired this alarm, or <c>null</c> when the firing change came from
|
||||
/// the Data Connection Layer (external data has no spawning execution).
|
||||
@@ -185,8 +183,8 @@ public class AlarmActor : ReceiveActor
|
||||
/// <param name="onTriggerExecutionTimeoutSeconds">The on-trigger script's per-script
|
||||
/// execution timeout in seconds (from its <see cref="ResolvedScript.ExecutionTimeoutSeconds"/>),
|
||||
/// or null/non-positive to use the global default.</param>
|
||||
/// <param name="scheduler">Optional script-execution scheduler override (#18); null uses the process-wide shared scheduler.</param>
|
||||
/// <param name="evalGate">Optional trigger-expression concurrency gate override (WP3.1); null uses the process-wide shared gate.</param>
|
||||
/// <param name="scheduler">Optional script-execution scheduler override; null uses the process-wide shared scheduler.</param>
|
||||
/// <param name="evalGate">Optional trigger-expression concurrency gate override; null uses the process-wide shared gate.</param>
|
||||
public AlarmActor(
|
||||
string alarmName,
|
||||
string instanceName,
|
||||
@@ -202,9 +200,9 @@ public class AlarmActor : ReceiveActor
|
||||
IServiceProvider? serviceProvider = null,
|
||||
// Per-script timeout for the on-trigger script (null = global).
|
||||
int? onTriggerExecutionTimeoutSeconds = null,
|
||||
// Script-execution scheduler seam (#18); null uses the process-wide shared scheduler.
|
||||
// Script-execution scheduler seam; null uses the process-wide shared scheduler.
|
||||
ScriptExecutionScheduler? scheduler = null,
|
||||
// WP3.1 trigger-eval gate seam; null uses the process-wide shared gate.
|
||||
// Trigger-eval gate seam; null uses the process-wide shared gate.
|
||||
TriggerEvalGate? evalGate = null)
|
||||
{
|
||||
_alarmName = alarmName;
|
||||
@@ -248,7 +246,7 @@ public class AlarmActor : ReceiveActor
|
||||
// Handle attribute value changes
|
||||
Receive<AttributeValueChanged>(HandleAttributeValueChanged);
|
||||
|
||||
// Handle alarm execution completion (also releases the WP3.1 in-flight slot)
|
||||
// Handle alarm execution completion (also releases the in-flight slot)
|
||||
Receive<AlarmExecutionCompleted>(HandleAlarmExecutionCompleted);
|
||||
|
||||
// Handle the off-dispatcher trigger-expression evaluation result (P1).
|
||||
@@ -268,7 +266,7 @@ public class AlarmActor : ReceiveActor
|
||||
_alarmName, _instanceName, _triggerType);
|
||||
}
|
||||
|
||||
// WP3.1: the Stop-on-failure SupervisorStrategy override is gone with the per-run
|
||||
// The Stop-on-failure SupervisorStrategy override is gone with the per-run
|
||||
// AlarmExecutionActor child it supervised. This actor has no children left; a
|
||||
// launch-path throw is caught in SpawnAlarmExecution so it can never escalate to
|
||||
// InstanceActor, which continues to supervise this actor with Resume exactly as before.
|
||||
@@ -613,7 +611,7 @@ public class AlarmActor : ReceiveActor
|
||||
// mis-attribute the raise this evaluation produces.
|
||||
var sourceExecutionId = _latestSourceExecutionId;
|
||||
var gate = _evalGate ?? TriggerEvalGate.Shared(_options);
|
||||
// WP3.1 (finding #4): the deadline clock starts AT ENQUEUE, on the actor thread —
|
||||
// The deadline clock starts AT ENQUEUE, on the actor thread —
|
||||
// gate-wait time burns the same budget, so a saturated gate yields a timely false
|
||||
// instead of an unbounded stall. And the work runs on the shared .NET thread pool,
|
||||
// NOT the blocking script pool, so it can never queue behind a blocked script body.
|
||||
@@ -747,7 +745,7 @@ public class AlarmActor : ReceiveActor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1: releases the in-flight slot for a completed on-trigger run. Every terminal
|
||||
/// Releases the in-flight slot for a completed on-trigger run. Every terminal
|
||||
/// path emits exactly one <see cref="AlarmExecutionCompleted"/> (success / timeout /
|
||||
/// failure / launch failure), so the counter tracks reality.
|
||||
/// </summary>
|
||||
@@ -760,7 +758,7 @@ public class AlarmActor : ReceiveActor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1: on-trigger runs launched but not yet completed. Exposed for regression
|
||||
/// On-trigger runs launched but not yet completed. Exposed for regression
|
||||
/// coverage of the shed cap.
|
||||
/// </summary>
|
||||
internal int RunsInFlight => _runsInFlight;
|
||||
@@ -770,7 +768,7 @@ public class AlarmActor : ReceiveActor
|
||||
/// Passes the firing alarm's level/priority/message so the script can
|
||||
/// branch on severity via the <c>Alarm</c> global.
|
||||
///
|
||||
/// <para>WP3.1: launched directly via <see cref="ScriptRunLauncher"/> rather than
|
||||
/// <para>Launched directly via <see cref="ScriptRunLauncher"/> rather than
|
||||
/// through a short-lived <c>AlarmExecutionActor</c> child, and bounded by
|
||||
/// <see cref="SiteRuntimeOptions.MaxConcurrentRunsPerScript"/> — a raise arriving while
|
||||
/// the cap is reached is shed (counted, rate-limited site event) rather than piling
|
||||
@@ -828,7 +826,7 @@ public class AlarmActor : ReceiveActor
|
||||
// Context.Parent.Tell from the execution actor.
|
||||
Self,
|
||||
_logger,
|
||||
// arch-review 2026-08-19 P1-3: same health collector + service provider as
|
||||
// Same health collector + service provider as
|
||||
// the instance-script path, so the alarm on-trigger run gets the same
|
||||
// stuck-script watchdog, script-error counting, and run-summary threading.
|
||||
_healthCollector,
|
||||
@@ -838,13 +836,13 @@ public class AlarmActor : ReceiveActor
|
||||
_onTriggerExecutionTimeoutSeconds,
|
||||
// The firing execution's id — null for DCL-originated changes.
|
||||
parentExecutionId,
|
||||
// Scheduler seam (#18): share this alarm's scheduler override with the
|
||||
// Scheduler seam: share this alarm's scheduler override with the
|
||||
// launched on-trigger script body (null = process-wide shared).
|
||||
_scheduler, Context.System.ActorSelection("/user/site-communication"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// WP3.1 supervision parity: mirrors the removed OneForOneStrategy's warn-and-stop
|
||||
// Supervision parity: mirrors the removed OneForOneStrategy's warn-and-stop
|
||||
// for a per-run child that failed to construct. The alarm continues.
|
||||
_logger.LogWarning(ex,
|
||||
"Alarm on-trigger execution launch for {Alarm} on {Instance} failed, stopping",
|
||||
@@ -854,7 +852,7 @@ public class AlarmActor : ReceiveActor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1 shed policy for alarm on-trigger runs: refuses the newest run when
|
||||
/// Shed policy for alarm on-trigger runs: refuses the newest run when
|
||||
/// <see cref="SiteRuntimeOptions.MaxConcurrentRunsPerScript"/> are already in flight.
|
||||
/// There is no Ask caller on this path (an on-trigger run is never awaited), so the
|
||||
/// shed surfaces as a counter plus a rate-limited Warning site event.
|
||||
|
||||
@@ -43,7 +43,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
private readonly DeployCompileValidator _deployCompileValidator;
|
||||
private readonly SharedScriptLibrary _sharedScriptLibrary;
|
||||
/// <summary>
|
||||
/// WP2.6a: the shared cache backing <c>SiteExternalSystemRepository</c> reads.
|
||||
/// The shared cache backing <c>SiteExternalSystemRepository</c> reads.
|
||||
/// Invalidated wholesale after <see cref="HandleDeployArtifacts"/> applies
|
||||
/// external-system changes so a script's next call re-reads fresh definitions/method
|
||||
/// lists instead of serving a stale cached snapshot. Null in tests that do not wire
|
||||
@@ -51,7 +51,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
/// </summary>
|
||||
private readonly ExternalSystemDefinitionCache? _externalSystemCache;
|
||||
/// <summary>
|
||||
/// Task 10 (site-secrets artifact deploy): per-node mirror reconciled synchronously by
|
||||
/// Per-node mirror reconciled synchronously by
|
||||
/// <see cref="HandleDeployArtifacts"/> right after the shipped secrets are written to
|
||||
/// <c>deployed_secrets</c>, so the reply's <c>SecretsApplied</c> reflects this node's own
|
||||
/// apply/verify outcome rather than the next periodic pass. Optional/null on nodes/tests
|
||||
@@ -97,7 +97,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
/// test can hold the read open mid-flight (row 101's ensure-started race tests).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Register P1-4: the row-67 ensure-started heal used to read through
|
||||
/// The ensure-started heal used to read through
|
||||
/// <see cref="_configLoader"/> — the same full-table-including-ConfigJson load the boot
|
||||
/// sequence uses — just to <c>FirstOrDefault</c> one instance. A volume-restore reconcile
|
||||
/// that raises hundreds of <see cref="EnsureInstanceStartedCommand"/>s in a row turned that
|
||||
@@ -202,7 +202,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
private readonly HashSet<string> _initFailedPendingRowRemoval = new();
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1 warm-then-gate: instances whose deploy compile is being warmed off the actor
|
||||
/// Warm-then-gate: instances whose deploy compile is being warmed off the actor
|
||||
/// thread, keyed by instance name. Presence is the per-instance in-flight guard that keeps
|
||||
/// same-instance command ordering intact while OTHER instances' commands flow freely.
|
||||
/// Entries are added in <see cref="HandleDeploy"/> and removed in
|
||||
@@ -212,7 +212,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
private readonly Dictionary<string, DeployWarmState> _deployWarms = new();
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1: script-execution scheduler seam (#18) — the pool this actor grows as instances
|
||||
/// Script-execution scheduler seam — the pool this actor grows as instances
|
||||
/// deploy. Null selects the process-wide shared scheduler; tests inject their own so
|
||||
/// sizing assertions do not perturb (or depend on) the process-wide singleton.
|
||||
/// </summary>
|
||||
@@ -236,7 +236,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
/// <summary>
|
||||
/// The stored revision hash the instance reconcile sweep has already ACTED on for an
|
||||
/// instance, keyed by instance name — <b>one repair attempt per (instance, revision)
|
||||
/// pair</b>, for both the row-78 start and the row-72 restart.
|
||||
/// pair</b>, for both the reconcile-sweep start and the snapshot-driven restart.
|
||||
/// <para>
|
||||
/// This is what keeps a periodic self-heal from becoming a restart loop. An instance whose
|
||||
/// Instance Actor dies during init leaves its config row in place, so without this the
|
||||
@@ -281,7 +281,6 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
/// <param name="options">Site runtime options (startup batch size, stagger interval, etc.).</param>
|
||||
/// <param name="logger">Logger instance.</param>
|
||||
/// <param name="dclManager">Optional Data Connection Layer manager actor.</param>
|
||||
/// <param name="replicationActor">Optional standby replication actor.</param>
|
||||
/// <param name="healthCollector">Optional site health collector.</param>
|
||||
/// <param name="serviceProvider">Optional service provider for resolving per-instance services.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory for creating Instance Actor loggers.</param>
|
||||
@@ -295,13 +294,13 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
/// <param name="configLoader">Optional override for loading all deployed configurations
|
||||
/// at startup; defaults to reading from <paramref name="storage"/>. Primarily for tests.</param>
|
||||
/// <param name="externalSystemCache">
|
||||
/// WP2.6a: the shared <see cref="ExternalSystemDefinitionCache"/> backing
|
||||
/// The shared <see cref="ExternalSystemDefinitionCache"/> backing
|
||||
/// <c>SiteExternalSystemRepository</c> reads, invalidated after this actor applies
|
||||
/// external-system changes. Optional/null in tests that do not exercise external-system
|
||||
/// caching.
|
||||
/// </param>
|
||||
/// <param name="scriptScheduler">
|
||||
/// WP3.1: optional script-execution scheduler override (#18). This actor grows the pool
|
||||
/// Optional script-execution scheduler override. This actor grows the pool
|
||||
/// towards <see cref="ScriptExecutionScheduler.ComputeTargetThreads"/> on every instance-
|
||||
/// count change; null uses the process-wide shared scheduler.
|
||||
/// </param>
|
||||
@@ -312,12 +311,12 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
/// drive a read failure.
|
||||
/// </param>
|
||||
/// <param name="deployedConfigLoader">
|
||||
/// Register P1-4: optional override for the single-row deployed-config read the row-67
|
||||
/// Optional override for the single-row deployed-config read the
|
||||
/// ensure-started heal uses; defaults to reading from <paramref name="storage"/>. Primarily
|
||||
/// for tests that need to hold the read open mid-flight to drive the row-101 race.
|
||||
/// for tests that need to hold the read open mid-flight to drive the freshness race.
|
||||
/// </param>
|
||||
/// <param name="secretsMirror">
|
||||
/// Task 10 (site-secrets artifact deploy): this node's <see cref="DeployedSecretsMirror"/>,
|
||||
/// This node's <see cref="DeployedSecretsMirror"/>,
|
||||
/// reconciled synchronously by <see cref="HandleDeployArtifacts"/> right after a shipped
|
||||
/// <c>command.Secrets</c> set is written to <c>deployed_secrets</c>. Optional/null on
|
||||
/// nodes/tests that never register secret deployment.
|
||||
@@ -378,10 +377,10 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
Receive<EnableInstanceCommand>(cmd => HandleEnable(cmd, Sender));
|
||||
Receive<DeleteInstanceCommand>(cmd => HandleDelete(cmd, Sender));
|
||||
|
||||
// WP3.1 warm-then-gate: the off-thread compile warm for a deploy has finished, so
|
||||
// Warm-then-gate: the off-thread compile warm for a deploy has finished, so
|
||||
// the (now all-cache-hit) synchronous compile gate can run on the actor thread.
|
||||
Receive<DeployCompileWarmed>(HandleDeployCompileWarmed);
|
||||
// WP3.1: a staggered startup batch's compiles have been pre-warmed off-thread;
|
||||
// A staggered startup batch's compiles have been pre-warmed off-thread;
|
||||
// create the batch's Instance Actors now that their PreStart compiles are hits.
|
||||
Receive<BatchCompileWarmed>(msg => CreateInstanceActorBatch(msg.Batch));
|
||||
|
||||
@@ -732,7 +731,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
|
||||
// Rows 89 + 90: seed the DCL arbitration overlay BEFORE any Instance Actor is created,
|
||||
// so the boot batch's EnsureDclConnections already resolves to the definition-owned
|
||||
// config. This is the failover half of the row-89 fix: a singleton incarnation starting
|
||||
// config. This is the failover half of the fix: a singleton incarnation starting
|
||||
// on the peer node rebuilds the whole DCL from flattened snapshots, and without the
|
||||
// overlay in place first it would rebuild them STALE and only correct itself a sweep
|
||||
// tick later — a needless create-then-diverge on every failover.
|
||||
@@ -764,7 +763,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
if (msg.EnabledConfigs.Count == 0)
|
||||
{
|
||||
// Nothing to create, so boot-time creation is trivially complete and the instance
|
||||
// reconcile sweep may run. This is exactly the row-78 live case: the node booted
|
||||
// reconcile sweep may run. This is exactly the live case: the node booted
|
||||
// with an empty deployed_configurations table and CDC replication seeds it moments
|
||||
// later, with no boot load left to notice.
|
||||
_startupInstanceCreationComplete = true;
|
||||
@@ -777,7 +776,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1: pre-warms one staggered startup batch's script and trigger-expression compiles
|
||||
/// Pre-warms one staggered startup batch's script and trigger-expression compiles
|
||||
/// off the actor thread, then pipes <see cref="BatchCompileWarmed"/> so
|
||||
/// <see cref="CreateInstanceActorBatch"/> can create the batch's Instance Actors with
|
||||
/// every <c>PreStart</c> compile already a cache hit.
|
||||
@@ -814,7 +813,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
catch
|
||||
{
|
||||
// Best-effort warm — a throw here just means that instance's PreStart
|
||||
// compiles the hard way, exactly as it did before WP3.1.
|
||||
// compiles the hard way, exactly as it did before the warm-then-gate change.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,7 +895,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
|
||||
|
||||
// WP3.1 warm-then-gate. The S3 gate below must stay synchronous on the actor thread
|
||||
// Warm-then-gate. The S3 gate below must stay synchronous on the actor thread
|
||||
// (mailbox FIFO is what makes redeploy-supersede and delete-during-redeploy correct),
|
||||
// but the Roslyn compile it performs used to hold the whole singleton — every OTHER
|
||||
// instance's commands stalled behind one instance's compile. So the compile is WARMED
|
||||
@@ -941,7 +940,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
/// <see cref="DeployCompileWarmed"/> back so the authoritative gate can run on the actor
|
||||
/// thread against a warm cache. The warm is best-effort: its verdict is discarded and any
|
||||
/// exception swallowed, because <see cref="RunDeployGateAndProceed"/> re-derives the
|
||||
/// verdict (and reproduces any failure) exactly as it did before WP3.1.
|
||||
/// verdict (and reproduces any failure) exactly as it did before the warm-then-gate change.
|
||||
/// </summary>
|
||||
private void StartDeployCompileWarm(DeployInstanceCommand command)
|
||||
{
|
||||
@@ -1044,7 +1043,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
// synchronously on the actor thread as a pure prefix step of the deploy
|
||||
// application, so the existing redeploy-supersede / delete-during-redeploy
|
||||
// ordering (which depends on strict mailbox FIFO) is preserved exactly.
|
||||
// WP3.1 removed the COST rather than the ordering: the same compile has
|
||||
// The warm-then-gate change removed the COST rather than the ordering: the same compile has
|
||||
// already been warmed off-thread (StartDeployCompileWarm), so this call is
|
||||
// pure cache hits and no longer holds the singleton — while the per-instance
|
||||
// warm guard keeps this instance's own command ordering intact. Redeploys and
|
||||
@@ -1345,7 +1344,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
// Static overrides and mirrored native alarm state are reset on redeployment per
|
||||
// design decision — the new config may bind different sources, and the source snapshot
|
||||
// re-seeds the mirror on (re)subscribe. Dispatched HERE, BEFORE the successor exists,
|
||||
// and handed to it as a gate (arch-review 2026-08-19 P1-2). It used to run inside the
|
||||
// and handed to it as a gate. It used to run inside the
|
||||
// persistence task below, i.e. after the actor was already created AND queued behind a
|
||||
// full config-JSON write, so the successor's PreStart override load routinely won the
|
||||
// race and applied the PREDECESSOR's overrides — including keys absent from the new
|
||||
@@ -1557,7 +1556,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
{
|
||||
var instanceName = command.InstanceUniqueName;
|
||||
|
||||
// WP3.1 warm-then-gate: a deploy for this instance is mid-compile-warm; queue this
|
||||
// Warm-then-gate: a deploy for this instance is mid-compile-warm; queue this
|
||||
// command so same-instance ordering is preserved (see TryBufferDuringDeployWarm).
|
||||
if (TryBufferDuringDeployWarm(instanceName, command, replyTo)) return;
|
||||
if (DeferLifecycleStop(instanceName, command.CommandId, false, replyTo)) return;
|
||||
@@ -1567,7 +1566,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
var sender = replyTo;
|
||||
|
||||
// Row 101 (initiation half): the actor has just stopped the Instance Actor while the
|
||||
// durable row still reads enabled, which is exactly the shape the row-78 sweep repairs.
|
||||
// durable row still reads enabled, which is exactly the shape the reconcile sweep repairs.
|
||||
// Any store read in flight would therefore "prove" a divergence that does not exist and
|
||||
// restart the instance the operator just disabled.
|
||||
BumpStoreWriteGeneration("disable dispatch");
|
||||
@@ -1617,7 +1616,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
|
||||
|
||||
// WP3.1 warm-then-gate: see TryBufferDuringDeployWarm.
|
||||
// Warm-then-gate: see TryBufferDuringDeployWarm.
|
||||
if (TryBufferDuringDeployWarm(instanceName, command, replyTo)) return;
|
||||
|
||||
var sender = replyTo;
|
||||
@@ -1628,7 +1627,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await _storage.SetInstanceEnabledAsync(instanceName, true);
|
||||
// Row P1-4: a single-row read — this used to materialize every deployed config
|
||||
// A single-row read — this used to materialize every deployed config
|
||||
// (including every ConfigJson blob) just to FirstOrDefault this one instance.
|
||||
var config = await _storage.GetDeployedConfigAsync(instanceName);
|
||||
return new EnableResult(command, config, null, sender);
|
||||
@@ -1722,7 +1721,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
var generationAtRead = _storeWriteGeneration;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// Register P1-4: read a single row through _deployedConfigLoader (defaults to
|
||||
// Read a single row through _deployedConfigLoader (defaults to
|
||||
// _storage.GetDeployedConfigAsync — a WHERE instance_unique_name = @name read, not
|
||||
// the full-table-including-ConfigJson load the boot sequence uses), which still lets
|
||||
// a test hold the read open and land the result behind a committed disable/delete.
|
||||
@@ -1821,7 +1820,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
|
||||
/// <summary>
|
||||
/// The one place an advisory self-heal turns a stored, enabled config into a running
|
||||
/// Instance Actor — shared by the row-67 ensure-started handler and the row-78 sweep so
|
||||
/// Instance Actor — shared by the ensure-started handler and the reconcile sweep so
|
||||
/// both produce identical bookkeeping and identical operator-facing surfacing. Callers
|
||||
/// are responsible for the guards; this method assumes they have passed.
|
||||
/// </summary>
|
||||
@@ -1915,7 +1914,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
|
||||
// Row 101, sweep half: this node mutated one of the two tables while the read was in
|
||||
// flight, so the snapshot predates state the actor has already advanced past. Discard it
|
||||
// WHOLE — no overlay apply, no row-78 start, no row-72 restart. Every repair this sweep
|
||||
// WHOLE — no overlay apply, no sweep start, no snapshot restart. Every repair this sweep
|
||||
// makes is derived from "the store says X, the runtime says Y", and a snapshot older
|
||||
// than the runtime inverts that comparison: it would restart an instance BACKWARD onto
|
||||
// the revision a deploy just replaced, or resurrect one a disable/delete just stopped.
|
||||
@@ -2099,7 +2098,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
{
|
||||
var instanceName = command.InstanceUniqueName;
|
||||
|
||||
// WP3.1 warm-then-gate: see TryBufferDuringDeployWarm.
|
||||
// Warm-then-gate: see TryBufferDuringDeployWarm.
|
||||
if (TryBufferDuringDeployWarm(instanceName, command, replyTo)) return;
|
||||
if (DeferLifecycleStop(instanceName, command.CommandId, true, replyTo)) return;
|
||||
|
||||
@@ -2191,7 +2190,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
var sender = Sender;
|
||||
var instanceName = request.InstanceUniqueName;
|
||||
|
||||
// Row P1-4: a single-row, ConfigJson-free projection — this used to materialize every
|
||||
// A single-row, ConfigJson-free projection — this used to materialize every
|
||||
// deployed config (including every ConfigJson blob) just to FirstOrDefault this one
|
||||
// instance's DeploymentId/RevisionHash. Neither is ever read here.
|
||||
_storage.GetDeployedConfigMetadataAsync(instanceName).ContinueWith(t =>
|
||||
@@ -2493,13 +2492,13 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
/// <item>DEFINITIONS half: absences "prove" just-applied names disappeared, tombstoning and
|
||||
/// STOPPING live connections that nothing re-creates until an instance redeploy; rows push
|
||||
/// superseded configs onto live connections and flip-flop with the next fresh read.</item>
|
||||
/// <item>CONFIGS half, row-72 leg: a snapshot carrying the revision a deploy has just
|
||||
/// <item>CONFIGS half, deploy-revision leg: a snapshot carrying the revision a deploy has just
|
||||
/// replaced makes <see cref="TryRestartStaleInstance"/> "repair" the instance BACKWARD —
|
||||
/// it redeploys the snapshot's own (old) config JSON through the redeploy path.
|
||||
/// <see cref="InstanceIsOwnedByADeployPipeline"/> does not cover this: it only holds while
|
||||
/// the deploy is in flight, and the stale snapshot can land after the pipeline released
|
||||
/// ownership.</item>
|
||||
/// <item>CONFIGS half, row-78 leg: a snapshot read before an undeploy/delete or a disable
|
||||
/// <item>CONFIGS half, stale-snapshot leg: a snapshot read before an undeploy/delete or a disable
|
||||
/// committed still shows an enabled row while the actor is already stopped, so
|
||||
/// <see cref="StartStoredInstance"/> RESURRECTS a just-stopped instance.</item>
|
||||
/// </list>
|
||||
@@ -2543,7 +2542,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The row-101 freshness test both snapshot consumers run BEFORE touching anything the
|
||||
/// The freshness test both snapshot consumers run BEFORE touching anything the
|
||||
/// snapshot carries. <see langword="true"/> means this actor mutated one of the two tables
|
||||
/// while the read was in flight, so the snapshot predates state the actor has already
|
||||
/// advanced past and must be discarded whole (see <see cref="_storeWriteGeneration"/> for
|
||||
@@ -3231,7 +3230,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// M2 (final review): the DCL push for this deploy's data connections is captured
|
||||
// The DCL push for this deploy's data connections is captured
|
||||
// here and sent only AFTER the shipped secrets are written and mirrored (in the
|
||||
// finally below), so a connection whose config references a secret this same deploy
|
||||
// ships resolves it on its FIRST connect instead of failing "not found" and waiting
|
||||
@@ -3279,7 +3278,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
await _storage.DeleteExternalSystemsExceptAsync(
|
||||
command.ExternalSystems.Select(es => es.Name).ToList());
|
||||
|
||||
// WP2.6a: drop the shared read cache so the next script call sees
|
||||
// Drop the shared read cache so the next script call sees
|
||||
// the freshly-applied definitions/method lists instead of a stale
|
||||
// snapshot loaded before this deploy.
|
||||
_externalSystemCache?.InvalidateAll();
|
||||
@@ -3335,7 +3334,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
// artifact bundle. Reconciled removals ride the same
|
||||
// message so the live DCL connection actor is stopped
|
||||
// on the actor thread alongside the hash-cache eviction.
|
||||
// Sent after the secrets step — see pendingDclApply above (M2).
|
||||
// Sent after the secrets step — see pendingDclApply above.
|
||||
pendingDclApply = new ApplyArtifactDataConnectionsToDcl(
|
||||
command.DataConnections, removedConnections);
|
||||
}
|
||||
@@ -3343,7 +3342,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
// SMTP configuration is
|
||||
// central-only and is never stored on a site (see the purge above).
|
||||
|
||||
// Site-secrets artifact deploy (Task 10): write the shipped secrets to the
|
||||
// Site-secrets artifact deploy: write the shipped secrets to the
|
||||
// replicated deployed_secrets table, then reconcile THIS node's own mirror
|
||||
// synchronously — the reply's SecretsApplied must reflect this node's own
|
||||
// apply/verify outcome, not whatever the next periodic pass finds. A null
|
||||
@@ -3465,7 +3464,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
/// </param>
|
||||
/// <param name="staticStateResetGate">
|
||||
/// The deploy path's static-override / native-alarm reset, dispatched BEFORE this call so the
|
||||
/// new actor's PreStart override load can be ordered after it (arch-review 2026-08-19 P1-2).
|
||||
/// new actor's PreStart override load can be ordered after it.
|
||||
/// <see langword="null"/> on every non-deploy start (boot batch, enable, self-heal), where
|
||||
/// nothing is being reset and the load runs immediately as before.
|
||||
/// </param>
|
||||
@@ -3479,13 +3478,13 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
//
|
||||
// This used to be the caller's job, and only TWO of the four callers did it: the
|
||||
// boot-time batch and ApplyDeployment. The two self-heal starts did not —
|
||||
// StartStoredInstance (the shared row-67 ensure-started / row-78 sweep path) and
|
||||
// StartStoredInstance (the shared ensure-started / reconcile sweep path) and
|
||||
// HandleEnableResult. Both therefore created an instance whose every tag subscribe was
|
||||
// answered "Unknown connection: X" by the DCL manager, forever, because nothing else on
|
||||
// the node would ever create that connection actor:
|
||||
//
|
||||
// - Row 85's live symptom (2026-08-16): a replication-seeded deployed_configurations row
|
||||
// is started by the row-78 sweep, but the connection actor is only ever created as a
|
||||
// is started by the reconcile sweep, but the connection actor is only ever created as a
|
||||
// side effect of a deploy or the boot load, so tag subscribes failed for 6+ minutes
|
||||
// until a second pair restart let the boot load create it.
|
||||
// - The same defect is reachable with no replication at all: the boot load reads only
|
||||
@@ -3587,7 +3586,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
internal int InstanceActorCount => _instanceActors.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the health collector with current instance counts, and (WP3.1) grows the
|
||||
/// Updates the health collector with current instance counts, and grows the
|
||||
/// script-execution pool to match.
|
||||
/// Total deployed = _deployedInstanceNames.Count, enabled = running actors, disabled = difference.
|
||||
/// </summary>
|
||||
@@ -3598,7 +3597,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
enabled: _instanceActors.Count,
|
||||
disabled: _deployedInstanceNames.Count - _instanceActors.Count);
|
||||
|
||||
// WP3.1: the blocking script pool scales with the number of running instances rather
|
||||
// The blocking script pool scales with the number of running instances rather
|
||||
// than sitting at a fixed 8 forever. This is the single call site because it already
|
||||
// runs on every deploy / undeploy / enable / disable and once per staggered startup
|
||||
// batch. Growth is idempotent and one-way — see ScriptExecutionScheduler.EnsureCapacity.
|
||||
@@ -3672,7 +3671,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
List<DeployedInstance> EnabledConfigs, int CompiledCount, int TotalCount);
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1 warm-then-gate: piped back to self once a deployment's scripts have been
|
||||
/// Warm-then-gate: piped back to self once a deployment's scripts have been
|
||||
/// compiled off the actor thread into the process-wide compile cache. Carries the exact
|
||||
/// command instance it warmed so a result superseded mid-warm is recognised by reference
|
||||
/// and dropped.
|
||||
@@ -3680,7 +3679,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
internal sealed record DeployCompileWarmed(DeployInstanceCommand Command);
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1: piped back to self once a staggered startup batch's compiles have been
|
||||
/// Piped back to self once a staggered startup batch's compiles have been
|
||||
/// pre-warmed, carrying the original batch message so actor creation resumes unchanged.
|
||||
/// </summary>
|
||||
internal sealed record BatchCompileWarmed(StartNextBatch Batch);
|
||||
|
||||
@@ -105,11 +105,11 @@ public class InstanceActor : ReceiveActor
|
||||
// deserialized configuration (last-wins on duplicate canonical names,
|
||||
// mirroring the rest of the actor's by-name dictionaries). Each entry
|
||||
// additionally carries its DataType/ElementDataType parsed ONCE at
|
||||
// config-apply time (perf remediation, arch-review WP1.5) so the hot
|
||||
// config-apply time so the hot
|
||||
// TagValueUpdate path never re-Enum.TryParse's a string per update.
|
||||
private readonly Dictionary<string, AttributeIndexEntry> _resolvedAttributeByName = new();
|
||||
|
||||
// Static-override write coalescing (perf remediation, arch-review WP1.5):
|
||||
// Static-override write coalescing:
|
||||
// mirrors NativeAlarmActor's MarkDirtyUpsert/FlushDirtyUpserts (P4) shape.
|
||||
// A burst of rapid SetAttribute calls on the same or different attributes
|
||||
// collapses into one batched SQLite transaction on a short single-shot
|
||||
@@ -124,7 +124,7 @@ public class InstanceActor : ReceiveActor
|
||||
/// <summary>
|
||||
/// The deploy path's static-override / native-alarm reset, supplied by
|
||||
/// <see cref="DeploymentManagerActor.CreateInstanceActor"/> on a (re)deploy and
|
||||
/// <see langword="null"/> on every other start (arch-review 2026-08-19 P1-2). When set,
|
||||
/// <see langword="null"/> on every other start When set,
|
||||
/// <see cref="PreStart"/> starts the override load only once it has completed, so a
|
||||
/// redeploy can never load the PREDECESSOR's overrides into the successor's memory.
|
||||
/// </summary>
|
||||
@@ -135,8 +135,7 @@ public class InstanceActor : ReceiveActor
|
||||
internal sealed record StaticOverridesFlushed(IActorRef Actor, Exception? Error);
|
||||
|
||||
/// <summary>
|
||||
/// Cached string forms of <see cref="QualityCode"/> (perf remediation, arch-review
|
||||
/// WP1.5) — the TagValueUpdate ingest path calls this once or twice per update, so a
|
||||
/// Cached string forms of <see cref="QualityCode"/> — the TagValueUpdate ingest path calls this once or twice per update, so a
|
||||
/// static lookup replaces repeated <see cref="Enum.ToString()"/> reflection.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<QualityCode, string> QualityStrings = new()
|
||||
@@ -150,7 +149,7 @@ public class InstanceActor : ReceiveActor
|
||||
QualityStrings.TryGetValue(quality, out var s) ? s : quality.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// O(1) attribute-resolution index entry (perf remediation, arch-review WP1.5): wraps a
|
||||
/// O(1) attribute-resolution index entry: wraps a
|
||||
/// <see cref="ResolvedAttribute"/> together with its <see cref="DataType"/> /
|
||||
/// element <see cref="DataType"/> / IsList classification, parsed ONCE when the entry
|
||||
/// is built (constructor) instead of re-parsed via <see cref="Enum.TryParse{DataType}"/>
|
||||
@@ -280,7 +279,7 @@ public class InstanceActor : ReceiveActor
|
||||
{
|
||||
// Parse DataType/ElementDataType ONCE here (config-apply time) and index
|
||||
// the wrapping entry for O(1) lookup on the hot TagValueUpdate ingest path
|
||||
// (last-wins on duplicate names) — perf remediation, arch-review WP1.5.
|
||||
// (last-wins on duplicate names)
|
||||
var dataType = ParseDataType(attr.DataType);
|
||||
var elementDataType = ParseElementDataType(attr.ElementDataType);
|
||||
var isList = dataType == DataType.List;
|
||||
@@ -398,8 +397,7 @@ public class InstanceActor : ReceiveActor
|
||||
|
||||
// Asynchronously load static overrides from SQLite and pipe to self. On a (re)deploy
|
||||
// the read is chained behind the reset the Deployment Manager dispatched before this
|
||||
// actor existed, so it can only ever observe the CLEARED table (arch-review
|
||||
// 2026-08-19 P1-2). A faulted reset does not block the load — the deploy itself fails
|
||||
// actor existed, so it can only ever observe the CLEARED table. A faulted reset does not block the load — the deploy itself fails
|
||||
// on that fault through the persistence join, which stops this actor anyway.
|
||||
var self = Self;
|
||||
var instanceName = _instanceUniqueName;
|
||||
@@ -519,7 +517,7 @@ public class InstanceActor : ReceiveActor
|
||||
private void HandleSetStaticAttribute(SetStaticAttributeCommand command)
|
||||
{
|
||||
// O(1) lookup via the existing hot-path index instead of a linear scan over
|
||||
// _configuration.Attributes (perf remediation, arch-review WP1.5).
|
||||
// _configuration.Attributes.
|
||||
_resolvedAttributeByName.TryGetValue(command.AttributeName, out var resolved);
|
||||
|
||||
// Reject writes targeting an attribute that does not exist
|
||||
@@ -640,7 +638,7 @@ public class InstanceActor : ReceiveActor
|
||||
|
||||
/// <summary>
|
||||
/// Records the latest value per attribute name and arms the coalesced flush timer
|
||||
/// (perf remediation, arch-review WP1.5) — mirrors NativeAlarmActor's
|
||||
/// — mirrors NativeAlarmActor's
|
||||
/// MarkDirtyUpsert/FlushDirtyUpserts (P4) shape. A burst of rapid static writes
|
||||
/// collapses into one batched SQLite transaction instead of a write per call.
|
||||
/// </summary>
|
||||
@@ -1157,7 +1155,7 @@ public class InstanceActor : ReceiveActor
|
||||
/// actor.
|
||||
///
|
||||
/// Takes the already-parsed <paramref name="dataType"/>/<paramref name="elementType"/>
|
||||
/// (perf remediation, arch-review WP1.5) rather than re-parsing from the attribute's
|
||||
/// rather than re-parsing from the attribute's
|
||||
/// raw DataType strings — every call site now resolves these once via
|
||||
/// <see cref="AttributeIndexEntry"/> at config-apply time.
|
||||
/// </summary>
|
||||
@@ -1185,8 +1183,7 @@ public class InstanceActor : ReceiveActor
|
||||
/// non-enumerable value, or any element that cannot be coerced — the caller then
|
||||
/// marks the attribute quality Bad. Never throws.
|
||||
///
|
||||
/// Takes the already-parsed <paramref name="elementType"/> (perf remediation,
|
||||
/// arch-review WP1.5) — see <see cref="DecodeAttributeValue"/>.
|
||||
/// Takes the already-parsed <paramref name="elementType"/> — see <see cref="DecodeAttributeValue"/>.
|
||||
/// </summary>
|
||||
private bool TryCoerceListValue(DataType? elementType, object? incoming, out object? typedList)
|
||||
{
|
||||
@@ -1325,13 +1322,18 @@ public class InstanceActor : ReceiveActor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pure backoff step for the tag-subscribe retry (Lever 1, 2026-08-28): double the
|
||||
/// Pure backoff step for the tag-subscribe retry: double the
|
||||
/// interval just used, apply ±20 % jitter from <paramref name="jitterUnit"/> (a
|
||||
/// uniform sample in [0,1]; 0.5 = no jitter) so N instances on one connection do not
|
||||
/// re-storm in lockstep, then clamp to [floor, ceiling]. A ceiling below the floor
|
||||
/// degrades to a fixed floor interval rather than shrinking below it — same contract
|
||||
/// as <c>DataConnectionActor.NextTagResolutionInterval</c>.
|
||||
/// </summary>
|
||||
/// <param name="current">The interval just used.</param>
|
||||
/// <param name="floor">The minimum interval.</param>
|
||||
/// <param name="max">The maximum interval; a value below <paramref name="floor"/> degrades to the floor.</param>
|
||||
/// <param name="jitterUnit">A uniform sample in [0,1] used to apply jitter; 0.5 means no jitter.</param>
|
||||
/// <returns>The next retry interval, clamped to the allowed range.</returns>
|
||||
internal static TimeSpan NextTagSubscribeInterval(TimeSpan current, TimeSpan floor, TimeSpan max, double jitterUnit)
|
||||
{
|
||||
var ceiling = max < floor ? floor : max;
|
||||
@@ -1658,7 +1660,7 @@ public class InstanceActor : ReceiveActor
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
// Alarms-only projection (WP2.3): the central live-alarm cache seeds/reconciles
|
||||
// Alarms-only projection: the central live-alarm cache seeds/reconciles
|
||||
// through this same query surface and throws every attribute row away. Building
|
||||
// and shipping them is pure waste — one instance with a few hundred attributes
|
||||
// costs more on the wire than the whole alarm set of the site. The flag is
|
||||
@@ -2153,7 +2155,7 @@ public class InstanceActor : ReceiveActor
|
||||
/// <param name="SourceExecutionId">Execution id of the writer that took the arm.</param>
|
||||
private sealed record DisarmTrace(string AttributeName, Guid SourceExecutionId);
|
||||
|
||||
/// <summary>Self-tell that fires the coalesced static-override persist flush (P4, arch-review WP1.5).</summary>
|
||||
/// <summary>Self-tell that fires the coalesced static-override persist flush.</summary>
|
||||
private sealed class FlushStaticOverrides
|
||||
{
|
||||
public static readonly FlushStaticOverrides Instance = new();
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors;
|
||||
/// Holds compiled script delegate, manages trigger configuration, and launches script
|
||||
/// runs per invocation. Does not block on run completion.
|
||||
///
|
||||
/// <para>WP3.1: runs are launched directly via <see cref="ScriptRunLauncher"/> rather than
|
||||
/// <para>Runs are launched directly via <see cref="ScriptRunLauncher"/> rather than
|
||||
/// through a short-lived <c>ScriptExecutionActor</c> child (which had no <c>Receive</c>
|
||||
/// handler, no <c>PostStop</c>, and whose <c>IActorRef</c> was never a message target — pure
|
||||
/// per-run actor-cell overhead). Concurrent runs are bounded by
|
||||
@@ -51,19 +51,19 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
private readonly IServiceProvider? _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Script-execution scheduler seam (#18): the process-wide
|
||||
/// Script-execution scheduler seam: the process-wide
|
||||
/// <see cref="ScriptExecutionScheduler"/> when null, or an injected instance so
|
||||
/// this actor's launched script bodies run on a caller-owned pool instead of the
|
||||
/// shared one. Resolved lazily at each use so the null (host) path stays
|
||||
/// byte-for-byte identical to the previous static call.
|
||||
///
|
||||
/// <para>WP3.1: trigger-expression evaluation no longer uses this scheduler — see
|
||||
/// <para>Trigger-expression evaluation no longer uses this scheduler — see
|
||||
/// <see cref="_evalGate"/>.</para>
|
||||
/// </summary>
|
||||
private readonly ScriptExecutionScheduler? _scheduler;
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1 (finding #4): the concurrency gate for trigger-expression evaluation, or null
|
||||
/// The concurrency gate for trigger-expression evaluation, or null
|
||||
/// for the process-wide <see cref="TriggerEvalGate.Shared"/>. Evaluations run as plain
|
||||
/// async work on the shared .NET thread pool behind this gate, NOT on
|
||||
/// <see cref="_scheduler"/> — that is what stops an Expression trigger from queueing
|
||||
@@ -72,7 +72,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
private readonly TriggerEvalGate? _evalGate;
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1: runs launched but not yet completed (queued or executing) for this script.
|
||||
/// Runs launched but not yet completed (queued or executing) for this script.
|
||||
/// Incremented at launch, decremented on <see cref="ScriptExecutionCompleted"/>, which
|
||||
/// every terminal path emits — including the launch-path catch, so the counter cannot
|
||||
/// leak. Touched only on the actor thread.
|
||||
@@ -80,7 +80,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
private int _runsInFlight;
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1: when the last shed Warning site event was emitted for this script. Sheds are
|
||||
/// When the last shed Warning site event was emitted for this script. Sheds are
|
||||
/// ALWAYS counted on the health collector; the site event is rate-limited to one per
|
||||
/// script per minute so a hot trigger against a saturated cap cannot flood
|
||||
/// <c>site_events</c>.
|
||||
@@ -152,8 +152,8 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
/// <param name="initialAttributes">Initial attribute snapshot used to seed expression trigger evaluation state.</param>
|
||||
/// <param name="healthCollector">Optional health metrics collector.</param>
|
||||
/// <param name="serviceProvider">Optional DI service provider for script execution context services.</param>
|
||||
/// <param name="scheduler">Optional script-execution scheduler override (#18); null uses the process-wide shared scheduler.</param>
|
||||
/// <param name="evalGate">Optional trigger-expression concurrency gate override (WP3.1); null uses the process-wide shared gate.</param>
|
||||
/// <param name="scheduler">Optional script-execution scheduler override; null uses the process-wide shared scheduler.</param>
|
||||
/// <param name="evalGate">Optional trigger-expression concurrency gate override; null uses the process-wide shared gate.</param>
|
||||
public ScriptActor(
|
||||
string scriptName,
|
||||
string instanceName,
|
||||
@@ -246,7 +246,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
_scriptName, _instanceName);
|
||||
}
|
||||
|
||||
// WP3.1: the Stop-on-failure SupervisorStrategy override is gone with the per-run
|
||||
// The Stop-on-failure SupervisorStrategy override is gone with the per-run
|
||||
// ScriptExecutionActor child it supervised. This actor has no children left; a
|
||||
// launch-path throw is caught in SpawnExecution (which replies to the Ask caller and
|
||||
// releases the in-flight slot) so it can never escalate to InstanceActor, which
|
||||
@@ -348,7 +348,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
/// pending, so a storm of changes collapses to the latest snapshot without
|
||||
/// unbounded task fan-out.
|
||||
///
|
||||
/// <para>WP3.1 (finding #4) changed WHERE it runs and WHEN its clock starts. It used
|
||||
/// <para>Trigger evaluation runs off the script pool and its clock starts at enqueue. It used
|
||||
/// to run on the dedicated <see cref="ScriptExecutionScheduler"/> — the same bounded
|
||||
/// pool as blocking script bodies — so N blocked scripts stalled every Expression
|
||||
/// trigger on the node indefinitely. It now runs as plain async work on the shared
|
||||
@@ -556,14 +556,14 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
/// <see cref="SiteRuntimeOptions.MaxConcurrentRunsPerScript"/>; beyond that the newest
|
||||
/// trigger is shed (see <see cref="ShedRun"/>).
|
||||
///
|
||||
/// <para>WP3.1: the run is launched directly on the script-execution scheduler via
|
||||
/// <para>The run is launched directly on the script-execution scheduler via
|
||||
/// <see cref="ScriptRunLauncher"/> — the actor's mailbox stays on the default dispatcher,
|
||||
/// but the script body runs on the bounded set of dedicated threads, so blocking script
|
||||
/// I/O is contained there and cannot starve the shared .NET thread pool. No per-run child
|
||||
/// actor is created.</para>
|
||||
///
|
||||
/// <para>The cap governs NEW work only (<paramref name="callDepth"/> 0 — trigger-driven
|
||||
/// runs and depth-0 Ask calls). A nested <c>CallScript</c> is exempt: the calling run is
|
||||
/// runs and top-level Ask calls). A nested <c>CallScript</c> is exempt: the calling run is
|
||||
/// itself still counted in <see cref="_runsInFlight"/> while it awaits its callee (the
|
||||
/// slot is released only by <see cref="ScriptExecutionCompleted"/>, sent after the body
|
||||
/// returns), and a script calling ITSELF routes back to this same actor — so counting the
|
||||
@@ -625,7 +625,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
parentExecutionId,
|
||||
// Per-script timeout override (null = use global).
|
||||
_executionTimeoutSeconds,
|
||||
// Scheduler seam (#18): thread this actor's scheduler override down so
|
||||
// Scheduler seam: thread this actor's scheduler override down so
|
||||
// launched script bodies share the same pool (null = process-wide shared).
|
||||
_scheduler,
|
||||
// Flow Debugger (design §5.2): trace flag + value-change trigger detail.
|
||||
@@ -634,7 +634,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// WP3.1 supervision parity: the only failure the removed per-run child could
|
||||
// Supervision parity: the only failure the removed per-run child could
|
||||
// surface was a constructor throw (e.g. queueing onto a disposed scheduler),
|
||||
// which the old OneForOneStrategy logged and stopped — leaving an Ask caller to
|
||||
// hang with no reply. Same log shape, same "coordinator unaffected" outcome, plus
|
||||
@@ -652,11 +652,11 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1 shed policy: refuses the incoming run because
|
||||
/// Shed policy: refuses the incoming run because
|
||||
/// <see cref="SiteRuntimeOptions.MaxConcurrentRunsPerScript"/> runs are already in flight.
|
||||
/// The four already queued/running are kept — they are closest to their own deadlines and
|
||||
/// already charged against them — so nothing is ever reordered. A trigger-driven run is
|
||||
/// simply not launched; a depth-0 Ask (an inbound-API route, or a <c>CallScript</c> from
|
||||
/// simply not launched; a top-level Ask (an inbound-API route, or a <c>CallScript</c> from
|
||||
/// an unrelated script's run) gets an explicit error so the caller fails fast rather than
|
||||
/// hanging to its Ask timeout. Nested (<c>callDepth > 0</c>) launches never reach here —
|
||||
/// see <see cref="SpawnExecution"/>.
|
||||
@@ -709,7 +709,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
|
||||
private void HandleExecutionCompleted(ScriptExecutionCompleted msg)
|
||||
{
|
||||
// WP3.1: release the in-flight slot. Every terminal path emits exactly one of these
|
||||
// Release the in-flight slot. Every terminal path emits exactly one of these
|
||||
// (success / timeout / failure / launch failure), so the counter tracks reality.
|
||||
if (_runsInFlight > 0) _runsInFlight--;
|
||||
|
||||
@@ -719,7 +719,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP3.1: runs launched but not yet completed. Exposed for regression coverage of the
|
||||
/// Runs launched but not yet completed. Exposed for regression coverage of the
|
||||
/// shed cap — the counter is otherwise invisible from outside the actor.
|
||||
/// </summary>
|
||||
internal int RunsInFlight => _runsInFlight;
|
||||
|
||||
Reference in New Issue
Block a user