private void Compact(long removalSizeTarget, Func <CacheEntry, long> computeEntrySize, CoherentState coherentState) { var entriesToRemove = new List <CacheEntry>(); // 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 DateTimeOffset now = _options.Clock.UtcNow; foreach (KeyValuePair <object, CacheEntry> item in coherentState._entries) { CacheEntry entry = item.Value; if (entry.CheckExpired(now)) { entriesToRemove.Add(entry); removedSize += computeEntrySize(entry); } else { switch (entry.Priority) { case CacheItemPriority.Low: lowPriEntries.Add((entry, entry.LastAccessed)); break; case CacheItemPriority.Normal: normalPriEntries.Add((entry, entry.LastAccessed)); break; case CacheItemPriority.High: highPriEntries.Add((entry, entry.LastAccessed)); break; case CacheItemPriority.NeverRemove: break; default: throw new NotSupportedException("Not implemented: " + entry.Priority); } } } ExpirePriorityBucket(ref removedSize, removalSizeTarget, computeEntrySize, entriesToRemove, lowPriEntries); ExpirePriorityBucket(ref removedSize, removalSizeTarget, computeEntrySize, entriesToRemove, normalPriEntries); ExpirePriorityBucket(ref removedSize, removalSizeTarget, computeEntrySize, entriesToRemove, highPriEntries); foreach (CacheEntry entry in entriesToRemove) { coherentState.RemoveEntry(entry, _options); }
private static void ScanForExpiredItems(MemoryCache cache) { DateTimeOffset now = cache._lastExpirationScan = cache._options.Clock.UtcNow; CoherentState coherentState = cache._coherentState; // Clear() can update the reference in the meantime foreach (KeyValuePair <object, CacheEntry> item in coherentState._entries) { CacheEntry entry = item.Value; if (entry.CheckExpired(now)) { coherentState.RemoveEntry(entry, cache._options); } } }
/// <inheritdoc /> public bool TryGetValue(object key, out object result) { ValidateCacheKey(key); CheckDisposed(); DateTimeOffset utcNow = _options.Clock.UtcNow; CoherentState coherentState = _coherentState; // Clear() can update the reference in the meantime if (coherentState._entries.TryGetValue(key, out CacheEntry entry)) { // Check if expired due to expiration tokens, timers, etc. and if so, remove it. // Allow a stale Replaced value to be returned due to concurrent calls to SetExpired during SetEntry. if (!entry.CheckExpired(utcNow) || entry.EvictionReason == EvictionReason.Replaced) { entry.LastAccessed = utcNow; result = entry.Value; if (TrackLinkedCacheEntries && entry.CanPropagateOptions()) { // When this entry is retrieved in the scope of creating another entry, // that entry needs a copy of these expiration tokens. entry.PropagateOptions(CacheEntryHelper.Current); } StartScanForExpiredItemsIfNeeded(utcNow); return(true); } else { // TODO: For efficiency queue this up for batch removal coherentState.RemoveEntry(entry, _options); } } StartScanForExpiredItemsIfNeeded(utcNow); result = null; return(false); }
internal void SetEntry(CacheEntry entry) { if (_disposed) { // No-op instead of throwing since this is called during CacheEntry.Dispose return; } if (_options.SizeLimit.HasValue && !entry.Size.HasValue) { throw new InvalidOperationException(SR.Format(SR.CacheEntryHasEmptySize, nameof(entry.Size), nameof(_options.SizeLimit))); } DateTimeOffset utcNow = _options.Clock.UtcNow; // Applying the option's absolute expiration only if it's not already smaller. // This can be the case if a dependent cache entry has a smaller value, and // it was set by cascading it to its parent. if (entry.AbsoluteExpirationRelativeToNow.HasValue) { var absoluteExpiration = utcNow + entry.AbsoluteExpirationRelativeToNow.Value; if (!entry.AbsoluteExpiration.HasValue || absoluteExpiration < entry.AbsoluteExpiration.Value) { entry.AbsoluteExpiration = absoluteExpiration; } } // Initialize the last access timestamp at the time the entry is added entry.LastAccessed = utcNow; CoherentState coherentState = _coherentState; // Clear() can update the reference in the meantime if (coherentState._entries.TryGetValue(entry.Key, out CacheEntry priorEntry)) { priorEntry.SetExpired(EvictionReason.Replaced); } if (entry.CheckExpired(utcNow)) { entry.InvokeEvictionCallbacks(); if (priorEntry != null) { coherentState.RemoveEntry(priorEntry, _options); } StartScanForExpiredItemsIfNeeded(utcNow); return; } bool exceedsCapacity = UpdateCacheSizeExceedsCapacity(entry, coherentState); if (!exceedsCapacity) { bool entryAdded = false; if (priorEntry == null) { // Try to add the new entry if no previous entries exist. entryAdded = coherentState._entries.TryAdd(entry.Key, entry); } else { // Try to update with the new entry if a previous entries exist. entryAdded = coherentState._entries.TryUpdate(entry.Key, entry, priorEntry); if (entryAdded) { if (_options.SizeLimit.HasValue) { // The prior entry was removed, decrease the by the prior entry's size Interlocked.Add(ref coherentState._cacheSize, -priorEntry.Size.Value); } } else { // The update will fail if the previous entry was removed after retrival. // Adding the new entry will succeed only if no entry has been added since. // This guarantees removing an old entry does not prevent adding a new entry. entryAdded = coherentState._entries.TryAdd(entry.Key, entry); } } if (entryAdded) { entry.AttachTokens(); } else { if (_options.SizeLimit.HasValue) { // Entry could not be added, reset cache size Interlocked.Add(ref coherentState._cacheSize, -entry.Size.Value); } entry.SetExpired(EvictionReason.Replaced); entry.InvokeEvictionCallbacks(); } if (priorEntry != null) { priorEntry.InvokeEvictionCallbacks(); } } else { entry.SetExpired(EvictionReason.Capacity); TriggerOvercapacityCompaction(); entry.InvokeEvictionCallbacks(); if (priorEntry != null) { coherentState.RemoveEntry(priorEntry, _options); } } StartScanForExpiredItemsIfNeeded(utcNow); }
internal void EntryExpired(CacheEntry entry) { // TODO: For efficiency consider processing these expirations in batches. _coherentState.RemoveEntry(entry, _options); StartScanForExpiredItemsIfNeeded(_options.Clock.UtcNow); }
/// <inheritdoc /> public bool TryGetValue(object key, out object?result) { ThrowHelper.ThrowIfNull(key); CheckDisposed(); DateTime utcNow = UtcNow; CoherentState coherentState = _coherentState; // Clear() can update the reference in the meantime if (coherentState._entries.TryGetValue(key, out CacheEntry? tmp)) { CacheEntry entry = tmp; // Check if expired due to expiration tokens, timers, etc. and if so, remove it. // Allow a stale Replaced value to be returned due to concurrent calls to SetExpired during SetEntry. if (!entry.CheckExpired(utcNow) || entry.EvictionReason == EvictionReason.Replaced) { entry.LastAccessed = utcNow; result = entry.Value; if (TrackLinkedCacheEntries) { // When this entry is retrieved in the scope of creating another entry, // that entry needs a copy of these expiration tokens. entry.PropagateOptionsToCurrent(); } StartScanForExpiredItemsIfNeeded(utcNow); // Hit if (_allStats is not null) { if (IntPtr.Size == 4) { Interlocked.Increment(ref GetStats().Hits); } else { GetStats().Hits++; } } return(true); } else { // TODO: For efficiency queue this up for batch removal coherentState.RemoveEntry(entry, _options); } } StartScanForExpiredItemsIfNeeded(utcNow); result = null; // Miss if (_allStats is not null) { if (IntPtr.Size == 4) { Interlocked.Increment(ref GetStats().Misses); } else { GetStats().Misses++; } } return(false); }