| File: CacheEntry.CacheEntryTokens.cs | Web Access |
| Project: src\runtime\src\libraries\Microsoft.Extensions.Caching.Memory\src\Microsoft.Extensions.Caching.Memory.csproj (Microsoft.Extensions.Caching.Memory) |
// 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.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; namespace Microsoft.Extensions.Caching.Memory { internal sealed partial class CacheEntry { // this type exists just to reduce average CacheEntry size // which typically is not using expiration tokens or callbacks private sealed class CacheEntryTokens { private List<IChangeToken>? _expirationTokens; private List<IDisposable>? _expirationTokenRegistrations; private List<PostEvictionCallbackRegistration>? _postEvictionCallbacks; // this is not really related to tokens, but was moved here to shrink typical CacheEntry size internal List<IChangeToken> ExpirationTokens => _expirationTokens ??= new List<IChangeToken>(); internal List<PostEvictionCallbackRegistration> PostEvictionCallbacks => _postEvictionCallbacks ??= new List<PostEvictionCallbackRegistration>(); internal void AttachTokens(CacheEntry cacheEntry) { List<IChangeToken>? expirationTokens = _expirationTokens; if (expirationTokens is not null) { lock (this) { for (int i = 0; i < expirationTokens.Count; i++) { IChangeToken expirationToken = expirationTokens[i]; if (expirationToken.ActiveChangeCallbacks) { _expirationTokenRegistrations ??= new List<IDisposable>(1); IDisposable registration = expirationToken.RegisterChangeCallback((Action<object?>)ExpirationCallback, cacheEntry); _expirationTokenRegistrations.Add(registration); } } } } } internal bool CheckForExpiredTokens(CacheEntry cacheEntry) { List<IChangeToken>? expirationTokens = _expirationTokens; if (expirationTokens is not null) { for (int i = 0; i < expirationTokens.Count; i++) { IChangeToken expiredToken = expirationTokens[i]; if (expiredToken.HasChanged) { cacheEntry.SetExpired(EvictionReason.TokenExpired); return true; } } } return false; } internal bool CanPropagateTokens() => _expirationTokens != null; internal void PropagateTokens(CacheEntry parentEntry) { if (_expirationTokens != null) { lock (this) { CacheEntryTokens parentTokens = parentEntry.GetOrCreateTokens(); lock (parentTokens) { parentTokens.ExpirationTokens.AddRange(_expirationTokens); } } } } internal void DetachTokens() { // _expirationTokenRegistrations is not checked for null, because AttachTokens might initialize it under lock // instead we are checking for _expirationTokens, because if they are not null, then _expirationTokenRegistrations might also be not null if (_expirationTokens != null) { lock (this) { List<IDisposable>? registrations = _expirationTokenRegistrations; if (registrations != null) { _expirationTokenRegistrations = null; for (int i = 0; i < registrations.Count; i++) { IDisposable registration = registrations[i]; registration.Dispose(); } } } } } internal void InvokeEvictionCallbacks(CacheEntry cacheEntry) { if (_postEvictionCallbacks != null) { Task.Factory.StartNew(state => InvokeCallbacks((CacheEntry)state!), cacheEntry, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } } private static void InvokeCallbacks(CacheEntry entry) { Debug.Assert(entry._tokens != null); List<PostEvictionCallbackRegistration>? callbackRegistrations = Interlocked.Exchange(ref entry._tokens._postEvictionCallbacks, null); if (callbackRegistrations == null) { return; } for (int i = 0; i < callbackRegistrations.Count; i++) { PostEvictionCallbackRegistration registration = callbackRegistrations[i]; try { registration.EvictionCallback?.Invoke(entry.Key, entry.Value, entry.EvictionReason, registration.State); } catch (Exception e) { // This will be invoked on a background thread, don't let it throw. entry._cache._logger.LogError(e, "EvictionCallback invoked failed"); } } } } } }