static void SetExpiredManyTimes(CacheEntry cacheEntry) { var now = DateTimeOffset.UtcNow; for (int i = 0; i < 1_000; i++) { cacheEntry.SetExpired(EvictionReason.Expired); // modifies CacheEntry._state Assert.True(cacheEntry.CheckExpired(now)); cacheEntry.Value = cacheEntry; // modifies CacheEntry._state Assert.True(cacheEntry.CheckExpired(now)); cacheEntry.SetExpired(EvictionReason.Expired); // modifies CacheEntry._state Assert.True(cacheEntry.CheckExpired(now)); cacheEntry.Dispose(); // might modify CacheEntry._state Assert.True(cacheEntry.CheckExpired(now)); } }
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); } } }
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 SetEntry(CacheEntry entry) { var utcNow = _clock.UtcNow; DateTimeOffset?absoluteExpiration = null; if (entry._absoluteExpirationRelativeToNow.HasValue) { absoluteExpiration = utcNow + entry._absoluteExpirationRelativeToNow; } else if (entry._absoluteExpiration.HasValue) { absoluteExpiration = entry._absoluteExpiration; } // 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 (absoluteExpiration.HasValue) { if (!entry._absoluteExpiration.HasValue || absoluteExpiration.Value < entry._absoluteExpiration.Value) { entry._absoluteExpiration = absoluteExpiration; } } // Initialize the last access timestamp at the time the entry is added entry.LastAccessed = utcNow; var added = false; CacheEntry priorEntry; _entryLock.EnterWriteLock(); try { if (_entries.TryGetValue(entry.Key, out priorEntry)) { _entries.Remove(entry.Key); priorEntry.SetExpired(EvictionReason.Replaced); } if (!entry.CheckExpired(utcNow)) { _entries[entry.Key] = entry; entry.AttachTokens(); added = true; } } finally { _entryLock.ExitWriteLock(); } if (priorEntry != null) { priorEntry.InvokeEvictionCallbacks(); } if (!added) { entry.InvokeEvictionCallbacks(); } StartScanForExpiredItems(); }
/// <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); }
public object Set(object key, object value, MemoryCacheEntryOptions cacheEntryOptions) { if (key == null) { throw new ArgumentNullException(nameof(key)); } CheckDisposed(); CacheEntry priorEntry = null; var utcNow = _clock.UtcNow; DateTimeOffset?absoluteExpiration = null; if (cacheEntryOptions.AbsoluteExpirationRelativeToNow.HasValue) { absoluteExpiration = utcNow + cacheEntryOptions.AbsoluteExpirationRelativeToNow; } else if (cacheEntryOptions.AbsoluteExpiration.HasValue) { if (cacheEntryOptions.AbsoluteExpiration <= utcNow) { throw new ArgumentOutOfRangeException( nameof(MemoryCacheEntryOptions.AbsoluteExpiration), cacheEntryOptions.AbsoluteExpiration.Value, "The absolute expiration value must be in the future."); } absoluteExpiration = cacheEntryOptions.AbsoluteExpiration; } var entry = new CacheEntry( key, value, utcNow, absoluteExpiration, cacheEntryOptions, _entryExpirationNotification); var link = EntryLinkHelpers.ContextLink; if (link != null) { // Copy expiration tokens and AbsoluteExpiration to the link. // We do this regardless of it gets cached because the tokens are associated with the value we'll return. if (entry.Options.ExpirationTokens != null) { link.AddExpirationTokens(entry.Options.ExpirationTokens); } if (absoluteExpiration.HasValue) { link.SetAbsoluteExpiration(absoluteExpiration.Value); } } bool added = false; _entryLock.EnterWriteLock(); try { if (_entries.TryGetValue(key, out priorEntry)) { _entries.Remove(key); priorEntry.SetExpired(EvictionReason.Replaced); } if (!entry.CheckExpired(utcNow)) { _entries[key] = entry; entry.AttachTokens(); added = true; } } finally { _entryLock.ExitWriteLock(); } if (priorEntry != null) { priorEntry.InvokeEvictionCallbacks(); } if (!added) { entry.InvokeEvictionCallbacks(); } StartScanForExpiredItems(); return(value); }
private void SetEntry(CacheEntry entry) { if (_disposed) { // No-op instead of throwing since this is called during CacheEntry.Dispose return; } var utcNow = _clock.UtcNow; DateTimeOffset?absoluteExpiration = null; if (entry._absoluteExpirationRelativeToNow.HasValue) { absoluteExpiration = utcNow + entry._absoluteExpirationRelativeToNow; } else if (entry._absoluteExpiration.HasValue) { absoluteExpiration = entry._absoluteExpiration; } // 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 (absoluteExpiration.HasValue) { if (!entry._absoluteExpiration.HasValue || absoluteExpiration.Value < entry._absoluteExpiration.Value) { entry._absoluteExpiration = absoluteExpiration; } } // Initialize the last access timestamp at the time the entry is added entry.LastAccessed = utcNow; CacheEntry priorEntry; if (_entries.TryGetValue(entry.Key, out priorEntry)) { priorEntry.SetExpired(EvictionReason.Replaced); } if (!entry.CheckExpired(utcNow)) { var entryAdded = false; if (priorEntry == null) { // Try to add the new entry if no previous entries exist. entryAdded = _entries.TryAdd(entry.Key, entry); } else { // Try to update with the new entry if a previous entries exist. entryAdded = _entries.TryUpdate(entry.Key, entry, priorEntry); if (!entryAdded) { // 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 = _entries.TryAdd(entry.Key, entry); } } if (entryAdded) { entry.AttachTokens(); } else { entry.SetExpired(EvictionReason.Replaced); entry.InvokeEvictionCallbacks(); } if (priorEntry != null) { priorEntry.InvokeEvictionCallbacks(); } } else { entry.InvokeEvictionCallbacks(); if (priorEntry != null) { RemoveEntry(priorEntry); } } StartScanForExpiredItems(); }
public object Set(object key, object value, MemoryCacheEntryOptions cacheEntryOptions) { if (key == null) { throw new ArgumentNullException(nameof(key)); } CheckDisposed(); CacheEntry priorEntry = null; var utcNow = _clock.UtcNow; DateTimeOffset? absoluteExpiration = null; if (cacheEntryOptions.AbsoluteExpirationRelativeToNow.HasValue) { absoluteExpiration = utcNow + cacheEntryOptions.AbsoluteExpirationRelativeToNow; } else if (cacheEntryOptions.AbsoluteExpiration.HasValue) { if (cacheEntryOptions.AbsoluteExpiration <= utcNow) { throw new ArgumentOutOfRangeException( nameof(MemoryCacheEntryOptions.AbsoluteExpiration), cacheEntryOptions.AbsoluteExpiration.Value, "The absolute expiration value must be in the future."); } absoluteExpiration = cacheEntryOptions.AbsoluteExpiration; } var entry = new CacheEntry( key, value, utcNow, absoluteExpiration, cacheEntryOptions, _entryExpirationNotification); var link = EntryLinkHelpers.ContextLink; if (link != null) { // Copy expiration tokens and AbsoluteExpiration to the link. // We do this regardless of it gets cached because the tokens are associated with the value we'll return. if (entry.Options.ExpirationTokens != null) { link.AddExpirationTokens(entry.Options.ExpirationTokens); } if (absoluteExpiration.HasValue) { link.SetAbsoluteExpiration(absoluteExpiration.Value); } } bool added = false; _entryLock.EnterWriteLock(); try { if (_entries.TryGetValue(key, out priorEntry)) { _entries.Remove(key); priorEntry.SetExpired(EvictionReason.Replaced); } if (!entry.CheckExpired(utcNow)) { _entries[key] = entry; entry.AttachTokens(); added = true; } } finally { _entryLock.ExitWriteLock(); } if (priorEntry != null) { priorEntry.InvokeEvictionCallbacks(); } if (!added) { entry.InvokeEvictionCallbacks(); } StartScanForExpiredItems(); return value; }