perf(dcl): batch subscribe/read/write seam, bounded reconnect, sharded subscriptions

This commit is contained in:
Joseph Doherty
2026-08-14 21:14:04 -04:00
parent ee193cd2bb
commit d15c5f02ea
25 changed files with 3131 additions and 324 deletions
@@ -11,6 +11,14 @@ public sealed class FakeMxGatewayClient : IMxGatewayClient, IMxGatewayClientFact
public MxGatewayConnectionOptions? ConnectedWith;
public readonly List<string> Subscribed = new();
public readonly List<string> Unsubscribed = new();
/// <summary>One entry per SubscribeBulkAsync call, carrying that call's tag list.</summary>
public readonly List<IReadOnlyList<string>> BulkSubscribeCalls = new();
/// <summary>One entry per UnsubscribeBulkAsync call, carrying that call's id list.</summary>
public readonly List<IReadOnlyList<string>> BulkUnsubscribeCalls = new();
/// <summary>Tags the fake reports as failed rows from a bulk subscribe.</summary>
public readonly HashSet<string> BulkSubscribeFailures = new(StringComparer.Ordinal);
/// <summary>Every alarm-stream prefix the adapter opened a stream with (null = gateway-wide).</summary>
public readonly List<string?> AlarmStreamPrefixes = new();
public readonly TaskCompletionSource EventLoopGate = new(TaskCreationOptions.RunContinuationsAsynchronously);
public Action<MxValueUpdate>? OnUpdate;
public Func<IReadOnlyList<string>, IReadOnlyList<MxReadOutcome>>? ReadHandler;
@@ -41,6 +49,34 @@ public sealed class FakeMxGatewayClient : IMxGatewayClient, IMxGatewayClientFact
return Task.CompletedTask;
}
public Task<IReadOnlyList<MxSubscribeOutcome>> SubscribeBulkAsync(
IReadOnlyList<string> tagPaths, CancellationToken ct = default)
{
BulkSubscribeCalls.Add(tagPaths.ToList());
var outcomes = new List<MxSubscribeOutcome>(tagPaths.Count);
foreach (var tag in tagPaths)
{
if (BulkSubscribeFailures.Contains(tag))
{
outcomes.Add(new MxSubscribeOutcome(tag, false, null, "not found"));
continue;
}
Subscribed.Add(tag);
outcomes.Add(new MxSubscribeOutcome(
tag, true, (++_nextHandle).ToString(), null));
}
return Task.FromResult<IReadOnlyList<MxSubscribeOutcome>>(outcomes);
}
public Task UnsubscribeBulkAsync(IReadOnlyList<string> subscriptionIds, CancellationToken ct = default)
{
BulkUnsubscribeCalls.Add(subscriptionIds.ToList());
Unsubscribed.AddRange(subscriptionIds);
return Task.CompletedTask;
}
public Task<IReadOnlyList<MxReadOutcome>> ReadAsync(IReadOnlyList<string> tags, CancellationToken ct = default)
=> Task.FromResult(ReadHandler!(tags));
@@ -62,7 +98,13 @@ public sealed class FakeMxGatewayClient : IMxGatewayClient, IMxGatewayClientFact
string? alarmFilterPrefix,
Action<ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms.NativeAlarmTransition> onTransition,
CancellationToken ct = default)
=> Task.CompletedTask; // no alarm feed in the fake
{
// No alarm feed in the fake — but the prefix each stream is opened with is
// recorded so the union-filter (longest-common-prefix) behaviour is testable.
lock (AlarmStreamPrefixes)
AlarmStreamPrefixes.Add(alarmFilterPrefix);
return Task.CompletedTask;
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;