示例#1
0
        /// <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);
        }
示例#2
0
        /// <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
        }
示例#3
0
        /// <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);
        }
示例#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);
        }
示例#5
0
        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);
            }
示例#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);
        }