fix(site-runtime,dcl): low-severity cleanups — observed adapter dispose (S9), alarm-filter overwrite warning (S10), bounded-channel comment (C2), invariant-culture condition fallback (C6)

This commit is contained in:
Joseph Doherty
2026-07-09 02:09:06 -04:00
parent 30886c9426
commit c9ac075e39
5 changed files with 84 additions and 8 deletions
@@ -229,7 +229,12 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
{
_log.Info("DataConnectionActor [{0}] stopping — disposing adapter", _connectionName);
_adapter.Disconnected -= OnAdapterDisconnected;
_ = _adapter.DisposeAsync().AsTask();
// S9: fire-and-forget, but observe a faulted dispose so a failing adapter
// teardown is logged rather than swallowed into an unobserved task exception.
_adapter.DisposeAsync().AsTask().ContinueWith(
t => _log.Warning("[{0}] Adapter dispose faulted on stop: {1}",
_connectionName, t.Exception?.GetBaseException().Message),
TaskContinuationOptions.OnlyOnFaulted);
}
/// <inheritdoc />
@@ -1829,6 +1834,17 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
_alarmSourceSubscribers[request.SourceReference] = subs;
}
subs.Add(subscriber);
// S10: the adapter feed carries a single shared condition filter per source.
// A second subscriber with a different filter silently overwrites the first
// (last-writer-wins). Surface it so a co-subscriber mismatch is diagnosable —
// co-subscribers to one source are expected to agree on the filter.
if (_alarmSourceFilter.TryGetValue(request.SourceReference, out var existingFilter)
&& !string.Equals(existingFilter, request.ConditionFilter, StringComparison.Ordinal))
{
_log.Warning(
"[{0}] Alarm condition filter for source {1} overwritten: '{2}' -> '{3}' (last subscriber wins; co-subscribers must agree)",
_connectionName, request.SourceReference, existingFilter, request.ConditionFilter);
}
_alarmSourceFilter[request.SourceReference] = request.ConditionFilter;
// Parse the type-name filter once; this is the authoritative client-side
// gate consulted on every routed transition.
@@ -212,11 +212,11 @@ public class SiteEventLogger : ISiteEventLogger, IDisposable
// on disk I/O or on contention for the write lock.
if (!_writeQueue.Writer.TryWrite(pending))
{
// The channel is unbounded, so the only way TryWrite fails is that the
// writer has been completed (logger disposed). The event cannot be
// persisted fault the Task rather than
// reporting false success, so a caller that awaits a critical audit
// event can tell it was dropped.
// The channel is bounded with DropOldest, so TryWrite only returns false
// once the channel is completed (logger disposed) — full-buffer pressure
// evicts the oldest pending event instead of failing the write. The event
// cannot be persisted — fault the Task rather than reporting false success,
// so a caller that awaits a critical audit event can tell it was dropped.
pending.Completion.TrySetException(
new ObjectDisposedException(nameof(SiteEventLogger),
"Event could not be recorded: the event logger has been disposed."));
@@ -475,7 +475,10 @@ public class ScriptActor : ReceiveActor, IWithTimers
_scriptName, _instanceName, msg.Success);
}
private static bool EvaluateCondition(ConditionalTriggerConfig config, object? value)
// internal (not private) so the culture-invariance of the non-numeric fallback
// can be unit-tested directly on the test thread — the live path evaluates on a
// dispatcher thread whose CurrentCulture the test cannot deterministically set.
internal static bool EvaluateCondition(ConditionalTriggerConfig config, object? value)
{
if (value == null) return false;
@@ -500,7 +503,10 @@ public class ScriptActor : ReceiveActor, IWithTimers
}
catch
{
return string.Equals(value.ToString(), config.Threshold.ToString(), StringComparison.Ordinal);
// Render the threshold with InvariantCulture to match the invariant numeric
// parse two lines up — otherwise a de-DE host renders 1.5 as "1,5" and the
// string fallback fires spuriously on locale-formatted values (C6).
return string.Equals(value.ToString(), config.Threshold.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal);
}
}