| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- using System;
- using System.Collections.Generic;
- using System.Runtime.CompilerServices;
- using System.Threading;
- using System.Threading.Tasks;
- using Microsoft.Extensions.Primitives;
- using Microsoft.Extensions.Caching.Memory;
- namespace appMpower.Memory
- {
- internal sealed partial class CacheEntry : ICacheEntry
- {
- private static readonly Action<object> ExpirationCallback = ExpirationTokensExpired;
- private readonly MemoryCache _cache;
- private CacheEntryTokens _tokens; // might be null if user is not using the tokens or callbacks
- private TimeSpan? _absoluteExpirationRelativeToNow;
- private TimeSpan? _slidingExpiration;
- private long? _size;
- private CacheEntry _previous; // this field is not null only before the entry is added to the cache and tracking is enabled
- private object _value;
- private CacheEntryState _state;
- internal CacheEntry(object key, MemoryCache memoryCache)
- {
- Key = key ?? throw new ArgumentNullException(nameof(key));
- _cache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache));
- _previous = memoryCache.TrackLinkedCacheEntries ? CacheEntryHelper.EnterScope(this) : null;
- _state = new CacheEntryState(CacheItemPriority.Normal);
- }
- /// <summary>
- /// Gets or sets an absolute expiration date for the cache entry.
- /// </summary>
- public DateTimeOffset? AbsoluteExpiration { get; set; }
- /// <summary>
- /// Gets or sets an absolute expiration time, relative to now.
- /// </summary>
- public TimeSpan? AbsoluteExpirationRelativeToNow
- {
- get => _absoluteExpirationRelativeToNow;
- set
- {
- // this method does not set AbsoluteExpiration as it would require calling Clock.UtcNow twice:
- // once here and once in MemoryCache.SetEntry
- if (value <= TimeSpan.Zero)
- {
- throw new ArgumentOutOfRangeException(
- nameof(AbsoluteExpirationRelativeToNow),
- value,
- "The relative expiration value must be positive.");
- }
- _absoluteExpirationRelativeToNow = value;
- }
- }
- /// <summary>
- /// Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed.
- /// This will not extend the entry lifetime beyond the absolute expiration (if set).
- /// </summary>
- public TimeSpan? SlidingExpiration
- {
- get => _slidingExpiration;
- set
- {
- if (value <= TimeSpan.Zero)
- {
- throw new ArgumentOutOfRangeException(
- nameof(SlidingExpiration),
- value,
- "The sliding expiration value must be positive.");
- }
- _slidingExpiration = value;
- }
- }
- /// <summary>
- /// Gets the <see cref="IChangeToken"/> instances which cause the cache entry to expire.
- /// </summary>
- public IList<IChangeToken> ExpirationTokens => GetOrCreateTokens().ExpirationTokens;
- /// <summary>
- /// Gets or sets the callbacks will be fired after the cache entry is evicted from the cache.
- /// </summary>
- public IList<PostEvictionCallbackRegistration> PostEvictionCallbacks => GetOrCreateTokens().PostEvictionCallbacks;
- /// <summary>
- /// Gets or sets the priority for keeping the cache entry in the cache during a
- /// memory pressure triggered cleanup. The default is <see cref="CacheItemPriority.Normal"/>.
- /// </summary>
- public CacheItemPriority Priority { get => _state.Priority; set => _state.Priority = value; }
- /// <summary>
- /// Gets or sets the size of the cache entry value.
- /// </summary>
- public long? Size
- {
- get => _size;
- set
- {
- if (value < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(value)} must be non-negative.");
- }
- _size = value;
- }
- }
- public object Key { get; private set; }
- public object Value
- {
- get => _value;
- set
- {
- _value = value;
- _state.IsValueSet = true;
- }
- }
- internal DateTimeOffset LastAccessed { get; set; }
- internal EvictionReason EvictionReason { get => _state.EvictionReason; private set => _state.EvictionReason = value; }
- public void Dispose()
- {
- if (!_state.IsDisposed)
- {
- _state.IsDisposed = true;
- if (_cache.TrackLinkedCacheEntries)
- {
- CacheEntryHelper.ExitScope(this, _previous);
- }
- // Don't commit or propagate options if the CacheEntry Value was never set.
- // We assume an exception occurred causing the caller to not set the Value successfully,
- // so don't use this entry.
- if (_state.IsValueSet)
- {
- _cache.SetEntry(this);
- if (_previous != null && CanPropagateOptions())
- {
- PropagateOptions(_previous);
- }
- }
- _previous = null; // we don't want to root unnecessary objects
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)] // added based on profiling
- internal bool CheckExpired(in DateTimeOffset now)
- => _state.IsExpired
- || CheckForExpiredTime(now)
- || (_tokens != null && _tokens.CheckForExpiredTokens(this));
- internal void SetExpired(EvictionReason reason)
- {
- if (EvictionReason == EvictionReason.None)
- {
- EvictionReason = reason;
- }
- _state.IsExpired = true;
- _tokens?.DetachTokens();
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)] // added based on profiling
- private bool CheckForExpiredTime(in DateTimeOffset now)
- {
- if (!AbsoluteExpiration.HasValue && !_slidingExpiration.HasValue)
- {
- return false;
- }
- return FullCheck(now);
- bool FullCheck(in DateTimeOffset offset)
- {
- if (AbsoluteExpiration.HasValue && AbsoluteExpiration.Value <= offset)
- {
- SetExpired(EvictionReason.Expired);
- return true;
- }
- if (_slidingExpiration.HasValue
- && (offset - LastAccessed) >= _slidingExpiration)
- {
- SetExpired(EvictionReason.Expired);
- return true;
- }
- return false;
- }
- }
- internal void AttachTokens() => _tokens?.AttachTokens(this);
- private static void ExpirationTokensExpired(object obj)
- {
- // start a new thread to avoid issues with callbacks called from RegisterChangeCallback
- Task.Factory.StartNew(state =>
- {
- var entry = (CacheEntry)state;
- entry.SetExpired(EvictionReason.TokenExpired);
- entry._cache.EntryExpired(entry);
- }, obj, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
- }
- internal void InvokeEvictionCallbacks() => _tokens?.InvokeEvictionCallbacks(this);
- // this simple check very often allows us to avoid expensive call to PropagateOptions(CacheEntryHelper.Current)
- [MethodImpl(MethodImplOptions.AggressiveInlining)] // added based on profiling
- internal bool CanPropagateOptions() => (_tokens != null && _tokens.CanPropagateTokens()) || AbsoluteExpiration.HasValue;
- internal void PropagateOptions(CacheEntry parent)
- {
- if (parent == null)
- {
- return;
- }
- // Copy expiration tokens and AbsoluteExpiration to the cache entries hierarchy.
- // We do this regardless of it gets cached because the tokens are associated with the value we'll return.
- _tokens?.PropagateTokens(parent);
- if (AbsoluteExpiration.HasValue)
- {
- if (!parent.AbsoluteExpiration.HasValue || AbsoluteExpiration < parent.AbsoluteExpiration)
- {
- parent.AbsoluteExpiration = AbsoluteExpiration;
- }
- }
- }
- private CacheEntryTokens GetOrCreateTokens()
- {
- if (_tokens != null)
- {
- return _tokens;
- }
- CacheEntryTokens result = new CacheEntryTokens();
- return Interlocked.CompareExchange(ref _tokens, result, null) ?? result;
- }
- }
- }
|