fix(mesh-phase5): telemetry hub — plain Dictionary under gate, concurrency stress test, priming/eviction notes
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
@@ -28,10 +28,18 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Telemetry;
|
||||
public sealed class TelemetryLocalHub : ITelemetryLocalHub
|
||||
{
|
||||
private readonly object _gate = new();
|
||||
private readonly ConcurrentDictionary<Guid, Channel<TelemetryItem>> _subscribers = new();
|
||||
|
||||
// Plain Dictionary, NOT ConcurrentDictionary: every access is already under _gate, and
|
||||
// ConcurrentDictionary.Values would materialize a fresh List (and take its own internal lock) on
|
||||
// every Emit — the hot node-actor-thread path. Under the gate a plain Dictionary is safe and
|
||||
// allocation-free to iterate.
|
||||
private readonly Dictionary<Guid, Channel<TelemetryItem>> _subscribers = new();
|
||||
|
||||
// Snapshot last-value caches. Only ever mutated under _gate (kept ConcurrentDictionary so a
|
||||
// Subscribe reading them under the gate is trivially safe even against any future lock-free read).
|
||||
// TODO(mesh-phase5+): evict snapshot cache entries on driver-instance removal (needs a
|
||||
// driver-lifecycle hook); pre-production, accepted for now. Until then a decommissioned driver
|
||||
// instance's last-known Health/Resilience state replays to new subscribers forever.
|
||||
private readonly ConcurrentDictionary<string, TelemetryItem.Health> _healthCache = new();
|
||||
private readonly ConcurrentDictionary<(string InstanceId, string HostName), TelemetryItem.Resilience> _resilienceCache = new();
|
||||
|
||||
@@ -91,7 +99,7 @@ public sealed class TelemetryLocalHub : ITelemetryLocalHub
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
if (_subscribers.TryRemove(id, out var channel))
|
||||
if (_subscribers.Remove(id, out var channel))
|
||||
channel.Writer.TryComplete();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user