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));
                }
            }
Exemple #2
0
        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);
            }
Exemple #3
0
        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);
                }
            }
        }
Exemple #4
0
        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);
        }
Exemple #5
0
        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();
        }
Exemple #6
0
        /// <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);
        }
Exemple #7
0
        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();
        }
Exemple #9
0
        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;
        }