/// <inheritdoc /> public void Remove(object key) { ValidateCacheKey(key); CheckDisposed(); CoherentState coherentState = _coherentState; // Clear() can update the reference in the meantime if (coherentState._entries.TryRemove(key, out CacheEntry entry)) { if (_options.SizeLimit.HasValue) { Interlocked.Add(ref coherentState._cacheSize, -entry.Size.Value); } entry.SetExpired(EvictionReason.Removed); entry.InvokeEvictionCallbacks(); } StartScanForExpiredItemsIfNeeded(_options.Clock.UtcNow); }
/// <summary> /// Creates a new <see cref="MemoryCache"/> instance. /// </summary> /// <param name="optionsAccessor">The options of the cache.</param> /// <param name="loggerFactory">The factory used to create loggers.</param> public MemoryCache(IOptions <MemoryCacheOptions> optionsAccessor, ILoggerFactory loggerFactory) { ThrowHelper.ThrowIfNull(optionsAccessor); ThrowHelper.ThrowIfNull(loggerFactory); _options = optionsAccessor.Value; _logger = loggerFactory.CreateLogger <MemoryCache>(); _coherentState = new CoherentState(); if (_options.TrackStatistics) { _allStats = new List <WeakReference <Stats> >(); _accumulatedStats = new Stats(); _stats = new ThreadLocal <Stats>(() => new Stats(this)); } _lastExpirationScan = UtcNow; TrackLinkedCacheEntries = _options.TrackLinkedCacheEntries; // we store the setting now so it's consistent for entire MemoryCache lifetime }
/// <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); }
private void Compact(long removalSizeTarget, Func <CacheEntry, long> computeEntrySize, CoherentState coherentState) { var entriesToRemove = new List <CacheEntry>(); var lowPriEntries = new List <CacheEntry>(); var normalPriEntries = new List <CacheEntry>(); var highPriEntries = new List <CacheEntry>(); 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); break; case CacheItemPriority.Normal: normalPriEntries.Add(entry); break; case CacheItemPriority.High: highPriEntries.Add(entry); 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); }
/// <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); }