예제 #1
0
        private static void InvokeCallbacks(CacheEntry entry)
        {
            var callbackRegistrations = entry.PostEvictionCallbacks;
            entry.PostEvictionCallbacks = null;

            if (callbackRegistrations == null)
            {
                return;
            }

            for (int i = 0; i < callbackRegistrations.Count; i++)
            {
                var registration = callbackRegistrations[i];

                try
                {
                    registration.EvictionCallback?.Invoke(entry.Key, entry.Value, entry.EvictionReason, registration.State);
                }
                catch (Exception)
                {
                    // This will be invoked on a background thread, don't let it throw.
                    // TODO: LOG
                }
            }
        }
예제 #2
0
 private void EntryExpired(CacheEntry entry)
 {
     // TODO: For efficiency consider processing these expirations in batches.
     RemoveEntry(entry);
     StartScanForExpiredItems();
 }
예제 #3
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();
        }
예제 #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;

                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
 internal void EntryExpired(CacheEntry entry)
 {
     // TODO: For efficiency consider processing these expirations in batches.
     _coherentState.RemoveEntry(entry, _options);
     StartScanForExpiredItemsIfNeeded(_options.Clock.UtcNow);
 }
예제 #6
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;
        }
예제 #7
0
 private void EntryExpired(CacheEntry entry)
 {
     // TODO: For efficiency consider processing these expirations in batches.
     RemoveEntry(entry);
     StartScanForExpiredItems();
 }
예제 #8
0
 private void RemoveEntry(CacheEntry entry)
 {
     _entryLock.EnterWriteLock();
     try
     {
         // Only remove it if someone hasn't modified it since our lookup
         CacheEntry currentEntry;
         if (_entries.TryGetValue(entry.Key, out currentEntry)
             && object.ReferenceEquals(currentEntry, entry))
         {
             _entries.Remove(entry.Key);
         }
     }
     finally
     {
         _entryLock.ExitWriteLock();
     }
     entry.InvokeEvictionCallbacks();
 }