namespace NATS.Server.Subscriptions; /// /// Per-account LRU cache for subscription match results. /// Avoids repeated SubList.Match() calls for the same subject. /// Uses atomic generation ID for bulk invalidation when subscriptions change. /// Go reference: server/client.go routeCache, maxResultCacheSize=8192. /// public sealed class RouteResultCache { private readonly int _capacity; private readonly Dictionary> _map; private readonly LinkedList<(string Key, SubListResult Value)> _list = new(); private readonly Lock _lock = new(); private long _generation; private long _cacheGeneration; // Stats private long _hits; private long _misses; public RouteResultCache(int capacity = 8192) { _capacity = capacity; _map = new Dictionary>(capacity, StringComparer.Ordinal); } public long Generation => Interlocked.Read(ref _generation); public long Hits => Interlocked.Read(ref _hits); public long Misses => Interlocked.Read(ref _misses); public int Count { get { lock (_lock) return _map.Count; } } /// /// Attempts to retrieve a cached result for the given subject. /// On generation mismatch (subscriptions changed since last cache fill), /// clears all entries and returns false. /// Updates LRU order on a hit. /// public bool TryGet(string subject, out SubListResult? result) { lock (_lock) { var currentGeneration = Interlocked.Read(ref _generation); if (_cacheGeneration != currentGeneration) { ClearUnderLock(); _cacheGeneration = currentGeneration; result = null; Interlocked.Increment(ref _misses); return false; } if (_map.TryGetValue(subject, out var node)) { // Move to front (most recently used) _list.Remove(node); _list.AddFirst(node); result = node.Value.Value; Interlocked.Increment(ref _hits); return true; } } result = null; Interlocked.Increment(ref _misses); return false; } /// /// Adds or updates a subject's cached result. /// If at capacity, evicts the least recently used entry (tail of list). /// public void Set(string subject, SubListResult result) { lock (_lock) { if (_map.TryGetValue(subject, out var existing)) { // Update value in place and move to front _list.Remove(existing); var updated = new LinkedListNode<(string Key, SubListResult Value)>((subject, result)); _list.AddFirst(updated); _map[subject] = updated; return; } // Evict LRU tail if at capacity if (_map.Count >= _capacity && _list.Last is { } tail) { _map.Remove(tail.Value.Key); _list.RemoveLast(); } var node = new LinkedListNode<(string Key, SubListResult Value)>((subject, result)); _list.AddFirst(node); _map[subject] = node; } } /// /// Increments the generation counter so that the next TryGet detects a mismatch /// and clears stale entries. This is called whenever subscriptions change. /// public void Invalidate() { Interlocked.Increment(ref _generation); } /// /// Immediately clears all cached entries under the lock. /// public void Clear() { lock (_lock) { ClearUnderLock(); } } private void ClearUnderLock() { _map.Clear(); _list.Clear(); } }