fix(data-connection-layer): resolve DataConnectionLayer-008,013 — O(1) unsubscribe via reverse index, atomic disconnect guard
This commit is contained in:
@@ -116,6 +116,45 @@ public class OpcUaDataConnectionTests
|
||||
Assert.Equal(ConnectionHealth.Disconnected, _adapter.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DCL013_ConcurrentConnectionLost_RaisesDisconnectedExactlyOnce()
|
||||
{
|
||||
// Regression test for DataConnectionLayer-013. RaiseDisconnected used a
|
||||
// non-atomic check-then-set on a volatile bool: two threads racing through it
|
||||
// (e.g. the keep-alive thread and a ReadAsync failure path, both routed via
|
||||
// OnClientConnectionLost) could both observe _disconnectFired == false and both
|
||||
// invoke Disconnected. The guard is now an atomic Interlocked.Exchange, so a
|
||||
// burst of concurrent connection-lost callbacks fires the event exactly once.
|
||||
// Repeat the burst: reconnecting between rounds re-arms the guard, so each
|
||||
// round must independently fire Disconnected exactly once. Repetition makes
|
||||
// the (timing-dependent) non-atomic race overwhelmingly likely to be caught.
|
||||
const int rounds = 25;
|
||||
const int threads = 32;
|
||||
for (var round = 0; round < rounds; round++)
|
||||
{
|
||||
_mockClient.IsConnected.Returns(true);
|
||||
await _adapter.ConnectAsync(new Dictionary<string, string>());
|
||||
|
||||
var fired = 0;
|
||||
void Handler() => Interlocked.Increment(ref fired);
|
||||
_adapter.Disconnected += Handler;
|
||||
|
||||
// Fan out: many threads raise the client's ConnectionLost event together.
|
||||
using (var ready = new Barrier(threads))
|
||||
{
|
||||
var tasks = Enumerable.Range(0, threads).Select(_ => Task.Run(() =>
|
||||
{
|
||||
ready.SignalAndWait();
|
||||
_mockClient.ConnectionLost += Raise.Event<Action>();
|
||||
})).ToArray();
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
_adapter.Disconnected -= Handler;
|
||||
Assert.Equal(1, fired);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Subscribe_DelegatesAndReturnsId()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user