diff --git a/src/Raven.Server/Utils/MemoryCache.cs b/src/Raven.Server/Utils/MemoryCache.cs index ff065fb56064..86c1c691e8ee 100644 --- a/src/Raven.Server/Utils/MemoryCache.cs +++ b/src/Raven.Server/Utils/MemoryCache.cs @@ -10,6 +10,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Lucene.Net.Util.Cache; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; @@ -24,7 +25,7 @@ public class MemoryCache : IMemoryCache internal readonly ILogger _logger; private readonly MemoryCacheOptions _options; - private readonly ConcurrentDictionary _entries; + private ConcurrentDictionary _entries; private long _cacheSize; private bool _disposed; @@ -85,7 +86,18 @@ public MemoryCache(IOptions optionsAccessor, ILoggerFactory private ICollection> EntriesCollection => _entries; - public void Clear() => _entries.Clear(); + public void Clear() + { + var oldEntries = _entries; + Interlocked.Exchange(ref _entries, new ConcurrentDictionary()); + Interlocked.Exchange(ref _cacheSize, 0); + + foreach (var entry in oldEntries) + { + entry.Value.SetExpired(EvictionReason.Removed); + entry.Value.InvokeEvictionCallbacks(); + } + } public IEnumerable> EntriesForDebug => _entries.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Value)); @@ -386,9 +398,10 @@ public void Compact(double percentage) private void Compact(long removalSizeTarget, Func computeEntrySize) { var entriesToRemove = new List(); - var lowPriEntries = new List(); - var normalPriEntries = new List(); - var highPriEntries = new List(); + // cache LastAccessed outside of the CacheEntry so it is stable during compaction + var lowPriEntries = new List<(CacheEntry entry, DateTimeOffset lastAccessed)>(); + var normalPriEntries = new List<(CacheEntry entry, DateTimeOffset lastAccessed)>(); + var highPriEntries = new List<(CacheEntry entry, DateTimeOffset lastAccessed)>(); long removedSize = 0; // Sort items by expired & priority status @@ -406,13 +419,13 @@ private void Compact(long removalSizeTarget, Func computeEntry switch (entry.Priority) { case CacheItemPriority.Low: - lowPriEntries.Add(entry); + lowPriEntries.Add((entry, entry.LastAccessed)); break; case CacheItemPriority.Normal: - normalPriEntries.Add(entry); + normalPriEntries.Add((entry, entry.LastAccessed)); break; case CacheItemPriority.High: - highPriEntries.Add(entry); + highPriEntries.Add((entry, entry.LastAccessed)); break; case CacheItemPriority.NeverRemove: break; @@ -437,7 +450,7 @@ private void Compact(long removalSizeTarget, Func computeEntry // ?. Items with the soonest sliding expiration. // ?. Larger objects - estimated by object graph size, inaccurate. static void ExpirePriorityBucket(ref long removedSize, long removalSizeTarget, Func computeEntrySize, List entriesToRemove, - List priorityEntries) + List<(CacheEntry Entry, DateTimeOffset LastAccessed)> priorityEntries) { // Do we meet our quota by just removing expired entries? if (removalSizeTarget <= removedSize) @@ -450,8 +463,8 @@ static void ExpirePriorityBucket(ref long removedSize, long removalSizeTarget, F // TODO: Refine policy // LRU - priorityEntries.Sort((e1, e2) => e1.LastAccessed.CompareTo(e2.LastAccessed)); - foreach (CacheEntry entry in priorityEntries) + priorityEntries.Sort( (e1, e2) => e1.LastAccessed.CompareTo(e2.LastAccessed)); + foreach ((CacheEntry entry,_) in priorityEntries) { entry.SetExpired(EvictionReason.Capacity); entriesToRemove.Add(entry);