Resolve all 46 low-severity architecture review findings

This commit is contained in:
Joseph Doherty
2026-09-09 22:56:36 -04:00
parent fef3e7675f
commit 9ee402bbcd
139 changed files with 2621 additions and 1082 deletions
@@ -64,7 +64,7 @@ public sealed class ScriptExecutionScheduler : TaskScheduler, IDisposable
/// </summary>
internal const int InstancesPerScriptThread = 8;
/// <summary>Thread-name prefix; also the inlining guard in <see cref="TryExecuteTaskInline"/>.</summary>
/// <summary>Thread-name prefix for diagnostics; inlining is guarded by scheduler identity.</summary>
private const string ThreadNamePrefix = "script-execution-";
private readonly BlockingCollection<Task> _queue = new();
@@ -338,8 +338,12 @@ public sealed class ScriptExecutionScheduler : TaskScheduler, IDisposable
}
}
[ThreadStatic]
private static ScriptExecutionScheduler? CurrentScheduler;
private void WorkerLoop(int index)
{
CurrentScheduler = this;
CurrentWorkerSlot = index;
var slot = _slots[index];
try
@@ -375,6 +379,7 @@ public sealed class ScriptExecutionScheduler : TaskScheduler, IDisposable
finally
{
CurrentWorkerSlot = null;
CurrentScheduler = null;
}
}
@@ -386,7 +391,7 @@ public sealed class ScriptExecutionScheduler : TaskScheduler, IDisposable
{
// Only inline if we are already on one of this scheduler's worker threads,
// so script work never escapes onto a thread-pool thread.
if (Thread.CurrentThread.Name?.StartsWith(ThreadNamePrefix, StringComparison.Ordinal) != true)
if (!ReferenceEquals(CurrentScheduler, this))
return false;
return TryExecuteTask(task);
@@ -411,7 +411,7 @@ public class ScriptRuntimeContext
{
// The collector is a debug-only observer: it must never fail a script call, and it
// must never keep the durable audit row from reaching the writer (review, 2026-09-06).
_logger.LogWarning(ex, "Flow trace collector rejected {Action} ({EventId})", evt.Action, evt.EventId);
_innerLogger.LogWarning(ex, "Flow trace collector rejected {Action} ({EventId})", evt.Action, evt.EventId);
}
}
@@ -548,7 +548,7 @@ public class ScriptRuntimeContext
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Flow trace event build failed for {Kind}", kind);
_innerLogger.LogWarning(ex, "Flow trace event build failed for {Kind}", kind);
return;
}
@@ -560,7 +560,7 @@ public class ScriptRuntimeContext
if (!t.IsCompletedSuccessfully)
{
_ = t.ContinueWith(
x => _logger.LogWarning(x.Exception, "Flow trace write failed for {Kind}", kind),
x => _innerLogger.LogWarning(x.Exception, "Flow trace write failed for {Kind}", kind),
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
@@ -568,7 +568,7 @@ public class ScriptRuntimeContext
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Flow trace write failed for {Kind}", kind);
_innerLogger.LogWarning(ex, "Flow trace write failed for {Kind}", kind);
}
}
@@ -1057,17 +1057,22 @@ public class ScriptRuntimeContext
return result.ReturnValue;
}
/// <summary>
/// Provides access to shared script execution via the Scripts property.
/// </summary>
public ScriptCallHelper Scripts => new(_sharedScriptLibrary, this, _currentCallDepth, _maxCallDepth, _logger);
private ScriptCallHelper? _scripts;
private ExternalSystemHelper? _externalSystem;
private DatabaseHelper? _database;
private NotifyHelper? _notify;
private AlarmsAccessor? _alarms;
private TrackingHelper? _tracking;
/// <summary>Provides access to shared script execution via a helper cached for this context.</summary>
public ScriptCallHelper Scripts => _scripts ??= new(_sharedScriptLibrary, this, _currentCallDepth, _maxCallDepth, _logger);
/// <summary>
/// Provides access to external system calls.
/// ExternalSystem.Call("systemName", "methodName", params)
/// ExternalSystem.CachedCall("systemName", "methodName", params)
/// </summary>
public ExternalSystemHelper ExternalSystem => new(
public ExternalSystemHelper ExternalSystem => _externalSystem ??= new(
_externalSystemClient, _instanceName, _logger, _executionId, _auditWriter, _siteId, _sourceScript,
// Emit CachedSubmit telemetry
// on every ExternalSystem.CachedCall enqueue.
@@ -1089,7 +1094,7 @@ public class ScriptRuntimeContext
/// Database.Connection("name")
/// Database.CachedWrite("name", "sql", params)
/// </summary>
public DatabaseHelper Database => new(
public DatabaseHelper Database => _database ??= new(
_databaseGateway,
_instanceName,
_logger,
@@ -1127,7 +1132,7 @@ public class ScriptRuntimeContext
/// Best-effort per alog.md §7 — a thrown writer never aborts the script's
/// <c>Send</c>.
/// </remarks>
public NotifyHelper Notify => new(
public NotifyHelper Notify => _notify ??= new(
_storeAndForward, _siteCommunicationActor, _siteId, _instanceName, _sourceScript, _askTimeout, _logger,
_executionId, _auditWriter,
// (ParentExecutionId): the spawning execution's id,
@@ -1146,7 +1151,7 @@ public class ScriptRuntimeContext
/// Gateway) conditions alike — so a <c>Call</c> script can answer "what is in alarm
/// right now?" without an on-trigger <c>Alarm</c> context.
/// </summary>
public AlarmsAccessor Alarms => new(this);
public AlarmsAccessor Alarms => _alarms ??= new(this);
/// <summary>
/// Backing read for <see cref="AlarmsAccessor.CurrentAsync"/>: Asks THIS instance's
@@ -1210,7 +1215,7 @@ public class ScriptRuntimeContext
/// returns a <see cref="TrackingStatusSnapshot"/>, or <c>null</c> when the
/// id is unknown / has already been purged.
/// </summary>
public TrackingHelper Tracking => new(_operationTrackingStore, _logger);
public TrackingHelper Tracking => _tracking ??= new(_operationTrackingStore, _logger);
/// <summary>
/// Helper class for Scripts.CallShared() syntax.
@@ -28,6 +28,7 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
public sealed class TriggerEvalGate : IDisposable
{
private readonly SemaphoreSlim _gate;
private bool _processOwned;
private static volatile TriggerEvalGate? _shared;
private static readonly object SharedLock = new();
@@ -48,7 +49,8 @@ public sealed class TriggerEvalGate : IDisposable
/// <summary>
/// The process-wide gate, used when no gate is injected. Lazily created from
/// <see cref="SiteRuntimeOptions.TriggerEvalMaxConcurrency"/>; the first caller wins.
/// <see cref="SiteRuntimeOptions.TriggerEvalMaxConcurrency"/>; the first caller wins. The shared gate is process-owned; calling
/// <see cref="Dispose"/> on it does not release the shared semaphore.
/// </summary>
/// <param name="options">Site runtime options supplying the concurrency limit.</param>
/// <returns>The shared gate instance.</returns>
@@ -59,7 +61,7 @@ public sealed class TriggerEvalGate : IDisposable
lock (SharedLock)
{
return _shared ??= new TriggerEvalGate(options.TriggerEvalMaxConcurrency);
return _shared ??= new TriggerEvalGate(options.TriggerEvalMaxConcurrency) { _processOwned = true };
}
}
@@ -76,6 +78,10 @@ public sealed class TriggerEvalGate : IDisposable
/// <summary>Returns a permit. Must be called exactly once per successful <see cref="WaitAsync"/>.</summary>
public void Release() => _gate.Release();
/// <inheritdoc />
public void Dispose() => _gate.Dispose();
/// <summary>Disposes a caller-created gate. The process-owned <see cref="Shared"/> gate remains available.</summary>
public void Dispose()
{
if (!_processOwned)
_gate.Dispose();
}
}