fix(galaxy): seam hardening - advise unmark on throw, dup attributeRef surfaced, bounded subscribe-once read; delete dormant historian connection probe (#605)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+6
-52
@@ -23,13 +23,6 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway;
|
||||
/// </remarks>
|
||||
public sealed class GatewayHistorianDataSource : IHistorianDataSource, IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ConnectionStatus.ConnectionKind"/> is a combinable [Flags] value: the
|
||||
/// process-data connection is bit 0 (value 1), the event connection is bit 1 (value 2).
|
||||
/// </summary>
|
||||
private const uint ProcessConnectionFlag = 1;
|
||||
private const uint EventConnectionFlag = 2;
|
||||
|
||||
private readonly IHistorianGatewayClient _client;
|
||||
private readonly ILogger<GatewayHistorianDataSource> _logger;
|
||||
|
||||
@@ -41,8 +34,6 @@ public sealed class GatewayHistorianDataSource : IHistorianDataSource, IAsyncDis
|
||||
private long _totalSuccesses;
|
||||
private long _totalFailures;
|
||||
private int _consecutiveFailures;
|
||||
private bool _processConnectionOpen;
|
||||
private bool _eventConnectionOpen;
|
||||
|
||||
/// <summary>Creates a gateway-backed historian data source.</summary>
|
||||
/// <param name="client">The gateway client seam used for all reads.</param>
|
||||
@@ -194,55 +185,18 @@ public sealed class GatewayHistorianDataSource : IHistorianDataSource, IAsyncDis
|
||||
LastSuccessTime: _lastSuccessUtc,
|
||||
LastFailureTime: _lastFailureUtc,
|
||||
LastError: _lastError,
|
||||
// Cached connection flags last observed by RefreshConnectionStateAsync. The gateway
|
||||
// is non-clustered to us, so node fields are null/empty (mirrors the Wonderware
|
||||
// client's Finding 010 posture).
|
||||
ProcessConnectionOpen: _processConnectionOpen,
|
||||
EventConnectionOpen: _eventConnectionOpen,
|
||||
// No connection probe exists (the old RefreshConnectionStateAsync had no production
|
||||
// caller and was deleted, archreview 06/S-6): "open" is derived from observed reads
|
||||
// alone -- at least one success and no failure since. The gateway is non-clustered to
|
||||
// us, so node fields are null/empty (mirrors the Wonderware client's Finding 010 posture).
|
||||
ProcessConnectionOpen: _totalSuccesses > 0 && _consecutiveFailures == 0,
|
||||
EventConnectionOpen: _totalSuccesses > 0 && _consecutiveFailures == 0,
|
||||
ActiveProcessNode: null,
|
||||
ActiveEventNode: null,
|
||||
Nodes: []);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the cached process / event connection flags by querying the gateway's
|
||||
/// connection status. Intended to be driven by a periodic health hosted-service, keeping
|
||||
/// <see cref="GetHealthSnapshot"/> pure observation (it never performs I/O). The flags are
|
||||
/// derived from <see cref="ConnectionStatus.ConnectedToServer"/> AND the matching
|
||||
/// <see cref="ConnectionStatus.ConnectionKind"/> flag bit. A failed status query is a health
|
||||
/// probe — it never throws to the caller; both flags degrade to closed until the next
|
||||
/// successful refresh.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A token to cancel the status query.</param>
|
||||
/// <returns>A task that completes when the cached flags have been updated.</returns>
|
||||
public async Task RefreshConnectionStateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
bool processOpen;
|
||||
bool eventOpen;
|
||||
try
|
||||
{
|
||||
var status = await _client.GetConnectionStatusAsync(cancellationToken).ConfigureAwait(false);
|
||||
var connected = status.ConnectedToServer;
|
||||
processOpen = connected && (status.ConnectionKind & ProcessConnectionFlag) != 0;
|
||||
eventOpen = connected && (status.ConnectionKind & EventConnectionFlag) != 0;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// A health probe must never crash the host; an unreachable gateway degrades both
|
||||
// connection flags to closed until the next successful refresh.
|
||||
_logger.LogDebug("Historian gateway connection-status refresh failed; treating both connections as closed.");
|
||||
processOpen = false;
|
||||
eventOpen = false;
|
||||
}
|
||||
|
||||
lock (_healthLock)
|
||||
{
|
||||
_processConnectionOpen = processOpen;
|
||||
_eventConnectionOpen = eventOpen;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reconciles a gateway at-time reply against the requested timestamps to honour the
|
||||
/// <see cref="IHistorianDataSource.ReadAtTimeAsync"/> contract: exactly one snapshot per
|
||||
|
||||
Reference in New Issue
Block a user