fix(data-connection-layer): resolve DataConnectionLayer-008,013 — O(1) unsubscribe via reverse index, atomic disconnect guard
This commit is contained in:
@@ -38,7 +38,12 @@ public class OpcUaDataConnection : IDataConnection
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private volatile bool _disconnectFired;
|
||||
// DataConnectionLayer-013: an int flag toggled with Interlocked.Exchange so the
|
||||
// "only the first caller fires Disconnected" guard in RaiseDisconnected is genuinely
|
||||
// atomic. A plain volatile bool gives visibility but not atomicity — two threads
|
||||
// (e.g. the keep-alive thread and a ReadAsync failure path) could both observe it
|
||||
// false and both raise the event. 0 = not fired, 1 = fired.
|
||||
private int _disconnectFired;
|
||||
|
||||
public ConnectionHealth Status => _status;
|
||||
public event Action? Disconnected;
|
||||
@@ -82,7 +87,7 @@ public class OpcUaDataConnection : IDataConnection
|
||||
await _client.ConnectAsync(_endpointUrl, options, cancellationToken);
|
||||
|
||||
_status = ConnectionHealth.Connected;
|
||||
_disconnectFired = false;
|
||||
Interlocked.Exchange(ref _disconnectFired, 0);
|
||||
_logger.LogInformation("OPC UA connected to {Endpoint}", _endpointUrl);
|
||||
|
||||
await StartHeartbeatMonitorAsync(config.Heartbeat, cancellationToken);
|
||||
@@ -285,12 +290,15 @@ public class OpcUaDataConnection : IDataConnection
|
||||
|
||||
/// <summary>
|
||||
/// Marks the connection as disconnected and fires the Disconnected event once.
|
||||
/// Thread-safe: only the first caller triggers the event.
|
||||
/// Thread-safe: the firing guard is an atomic compare-and-set
|
||||
/// (<see cref="Interlocked.Exchange(ref int, int)"/>), so when several threads race
|
||||
/// here — e.g. the keep-alive thread via <see cref="OnClientConnectionLost"/> and a
|
||||
/// <c>ReadAsync</c> failure path — exactly one of them observes the 0→1 transition
|
||||
/// and invokes <see cref="Disconnected"/>.
|
||||
/// </summary>
|
||||
private void RaiseDisconnected()
|
||||
{
|
||||
if (_disconnectFired) return;
|
||||
_disconnectFired = true;
|
||||
if (Interlocked.Exchange(ref _disconnectFired, 1) != 0) return;
|
||||
_status = ConnectionHealth.Disconnected;
|
||||
_logger.LogWarning("OPC UA connection to {Endpoint} lost", _endpointUrl);
|
||||
Disconnected?.Invoke();
|
||||
|
||||
Reference in New Issue
Block a user