_lastPublishedByRef was a plain Dictionary<string, object> mutated inside ShouldPublish, which runs on the PollGroupEngine onChange callback. The engine runs one background Task per subscription, so a driver with two or more subscriptions invokes ShouldPublish concurrently on separate threads. Concurrent TryGetValue/indexer writes on a non-thread-safe Dictionary can corrupt internal state, drop entries, or throw, crashing the poll loop. Switch _lastPublishedByRef to ConcurrentDictionary<string, object>; its TryGetValue and indexer-set operations are individually thread-safe, so the deadband cache is now correct under concurrent multi-subscription publishing, consistent with the lock-guarded sibling cache _lastWrittenByRef. Add an xUnit + Shouldly regression test that runs 24 deadband-configured single-tag subscriptions concurrently and asserts the poll loop survives without faulting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
11 KiB