fix(lmxproxy): support multiple subscriptions per session
Key subscriptions by unique subscriptionId instead of sessionId to prevent overwrites when the same session calls Subscribe multiple times (e.g. DCL StaleTagMonitor). Add session-to-subscription reverse lookup for cleanup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -51,8 +51,9 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Subscriptions
|
||||
{
|
||||
using var sm = new SubscriptionManager(new FakeScadaClient());
|
||||
using var cts = new CancellationTokenSource();
|
||||
var reader = await sm.SubscribeAsync("client1", new[] { "Tag1", "Tag2" }, cts.Token);
|
||||
var (reader, subscriptionId) = await sm.SubscribeAsync("client1", new[] { "Tag1", "Tag2" }, cts.Token);
|
||||
reader.Should().NotBeNull();
|
||||
subscriptionId.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -60,7 +61,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Subscriptions
|
||||
{
|
||||
using var sm = new SubscriptionManager(new FakeScadaClient());
|
||||
using var cts = new CancellationTokenSource();
|
||||
var reader = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
var (reader, _) = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
|
||||
var vtq = Vtq.Good(42.0);
|
||||
sm.OnTagValueChanged("Motor.Speed", vtq);
|
||||
@@ -76,8 +77,8 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Subscriptions
|
||||
{
|
||||
using var sm = new SubscriptionManager(new FakeScadaClient());
|
||||
using var cts = new CancellationTokenSource();
|
||||
var reader1 = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
var reader2 = await sm.SubscribeAsync("client2", new[] { "Motor.Speed" }, cts.Token);
|
||||
var (reader1, _) = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
var (reader2, _) = await sm.SubscribeAsync("client2", new[] { "Motor.Speed" }, cts.Token);
|
||||
|
||||
sm.OnTagValueChanged("Motor.Speed", Vtq.Good(99.0));
|
||||
|
||||
@@ -92,7 +93,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Subscriptions
|
||||
{
|
||||
using var sm = new SubscriptionManager(new FakeScadaClient());
|
||||
using var cts = new CancellationTokenSource();
|
||||
var reader = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
var (reader, _) = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
|
||||
sm.OnTagValueChanged("Motor.Torque", Vtq.Good(10.0));
|
||||
|
||||
@@ -101,26 +102,26 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Subscriptions
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnsubscribeClient_CompletesChannel()
|
||||
public async Task UnsubscribeSubscription_CompletesChannel()
|
||||
{
|
||||
using var sm = new SubscriptionManager(new FakeScadaClient());
|
||||
using var cts = new CancellationTokenSource();
|
||||
var reader = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
var (reader, subscriptionId) = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
|
||||
sm.UnsubscribeClient("client1");
|
||||
sm.UnsubscribeSubscription(subscriptionId);
|
||||
|
||||
// Channel should be completed
|
||||
reader.Completion.IsCompleted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnsubscribeClient_RemovesFromTagSubscriptions()
|
||||
public async Task UnsubscribeSession_RemovesAllSubscriptions()
|
||||
{
|
||||
using var sm = new SubscriptionManager(new FakeScadaClient());
|
||||
using var cts = new CancellationTokenSource();
|
||||
await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
|
||||
sm.UnsubscribeClient("client1");
|
||||
sm.UnsubscribeSession("client1");
|
||||
|
||||
var stats = sm.GetStats();
|
||||
stats.TotalClients.Should().Be(0);
|
||||
@@ -128,20 +129,20 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Subscriptions
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefCounting_LastClientUnsubscribeRemovesTag()
|
||||
public async Task RefCounting_LastSubscriptionUnsubscribeRemovesTag()
|
||||
{
|
||||
using var sm = new SubscriptionManager(new FakeScadaClient());
|
||||
using var cts = new CancellationTokenSource();
|
||||
await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
await sm.SubscribeAsync("client2", new[] { "Motor.Speed" }, cts.Token);
|
||||
var (_, subId1) = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
var (_, subId2) = await sm.SubscribeAsync("client2", new[] { "Motor.Speed" }, cts.Token);
|
||||
|
||||
sm.GetStats().TotalTags.Should().Be(1);
|
||||
|
||||
sm.UnsubscribeClient("client1");
|
||||
sm.UnsubscribeSubscription(subId1);
|
||||
sm.GetStats().TotalTags.Should().Be(1); // client2 still subscribed
|
||||
|
||||
sm.UnsubscribeClient("client2");
|
||||
sm.GetStats().TotalTags.Should().Be(0); // last client gone
|
||||
sm.UnsubscribeSubscription(subId2);
|
||||
sm.GetStats().TotalTags.Should().Be(0); // last subscription gone
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -149,7 +150,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Subscriptions
|
||||
{
|
||||
using var sm = new SubscriptionManager(new FakeScadaClient());
|
||||
using var cts = new CancellationTokenSource();
|
||||
var reader = await sm.SubscribeAsync("client1", new[] { "Motor.Speed", "Motor.Torque" }, cts.Token);
|
||||
var (reader, _) = await sm.SubscribeAsync("client1", new[] { "Motor.Speed", "Motor.Torque" }, cts.Token);
|
||||
|
||||
sm.NotifyDisconnection();
|
||||
|
||||
@@ -165,7 +166,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Subscriptions
|
||||
{
|
||||
using var sm = new SubscriptionManager(new FakeScadaClient(), channelCapacity: 3);
|
||||
using var cts = new CancellationTokenSource();
|
||||
var reader = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
var (reader, _) = await sm.SubscribeAsync("client1", new[] { "Motor.Speed" }, cts.Token);
|
||||
|
||||
// Fill the channel beyond capacity
|
||||
for (int i = 0; i < 10; i++)
|
||||
@@ -184,8 +185,8 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Subscriptions
|
||||
{
|
||||
using var sm = new SubscriptionManager(new FakeScadaClient());
|
||||
using var cts = new CancellationTokenSource();
|
||||
await sm.SubscribeAsync("c1", new[] { "Tag1", "Tag2" }, cts.Token);
|
||||
await sm.SubscribeAsync("c2", new[] { "Tag2", "Tag3" }, cts.Token);
|
||||
var (_, _) = await sm.SubscribeAsync("c1", new[] { "Tag1", "Tag2" }, cts.Token);
|
||||
var (_, _) = await sm.SubscribeAsync("c2", new[] { "Tag2", "Tag3" }, cts.Token);
|
||||
|
||||
var stats = sm.GetStats();
|
||||
stats.TotalClients.Should().Be(2);
|
||||
|
||||
Reference in New Issue
Block a user