fix(data-connection-layer): resolve DataConnectionLayer-008,013 — O(1) unsubscribe via reverse index, atomic disconnect guard
This commit is contained in:
@@ -24,7 +24,10 @@ public class RealOpcUaClient : IOpcUaClient
|
||||
// Clear() is undefined behaviour, so they must be ConcurrentDictionary.
|
||||
private readonly ConcurrentDictionary<string, MonitoredItem> _monitoredItems = new();
|
||||
private readonly ConcurrentDictionary<string, Action<string, object?, DateTime, uint>> _callbacks = new();
|
||||
private volatile bool _connectionLostFired;
|
||||
// DataConnectionLayer-013: int flag toggled with Interlocked.Exchange so the
|
||||
// once-only ConnectionLost guard in OnSessionKeepAlive is atomic, not just visible.
|
||||
// 0 = not fired, 1 = fired.
|
||||
private int _connectionLostFired;
|
||||
private OpcUaConnectionOptions _options = new();
|
||||
private readonly OpcUaGlobalOptions _globalOptions;
|
||||
private readonly ILogger<RealOpcUaClient> _logger;
|
||||
@@ -112,7 +115,7 @@ public class RealOpcUaClient : IOpcUaClient
|
||||
"ScadaLink-DCL-Session", (uint)opts.SessionTimeoutMs, userIdentity, null, cancellationToken);
|
||||
|
||||
// Detect server going offline via keep-alive failures
|
||||
_connectionLostFired = false;
|
||||
Interlocked.Exchange(ref _connectionLostFired, 0);
|
||||
_session.KeepAlive += OnSessionKeepAlive;
|
||||
|
||||
// Store options for monitored item creation
|
||||
@@ -243,14 +246,15 @@ public class RealOpcUaClient : IOpcUaClient
|
||||
|
||||
/// <summary>
|
||||
/// Called by the OPC UA SDK when a keep-alive response arrives (or fails).
|
||||
/// When CurrentState is bad, the server is unreachable.
|
||||
/// When CurrentState is bad, the server is unreachable. The once-only guard is an
|
||||
/// atomic compare-and-set, so a burst of failed keep-alives raises
|
||||
/// <see cref="ConnectionLost"/> exactly once.
|
||||
/// </summary>
|
||||
private void OnSessionKeepAlive(ISession session, KeepAliveEventArgs e)
|
||||
{
|
||||
if (ServiceResult.IsBad(e.Status))
|
||||
{
|
||||
if (_connectionLostFired) return;
|
||||
_connectionLostFired = true;
|
||||
if (Interlocked.Exchange(ref _connectionLostFired, 1) != 0) return;
|
||||
ConnectionLost?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user