File: Internal\DefaultHybridCache.StampedeState.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.Caching.Hybrid\Microsoft.Extensions.Caching.Hybrid.csproj (Microsoft.Extensions.Caching.Hybrid)
// 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.Threading;
 
#if !NETCOREAPP3_0_OR_GREATER
using System.Runtime.CompilerServices;
#endif
 
namespace Microsoft.Extensions.Caching.Hybrid.Internal;
 
internal partial class DefaultHybridCache
{
    internal abstract class StampedeState
#if NETCOREAPP3_0_OR_GREATER
        : IThreadPoolWorkItem
#endif
    {
        internal readonly CancellationToken SharedToken; // this might have a value even when _sharedCancellation is null
 
        // Because multiple callers can enlist, we need to track when the *last* caller cancels
        // (and keep going until then); that means we need to run with custom cancellation.
        private readonly CancellationTokenSource? _sharedCancellation;
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0032:Use auto property", Justification = "Keep usage explicit")]
        private readonly DefaultHybridCache _cache;
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0032:Use auto property", Justification = "Keep usage explicit")]
        private readonly CacheItem _cacheItem;
 
        // we expose the key as a by-ref readonly; this minimizes the stack work involved in passing the key around
        // (both in terms of width and copy-semantics)
        private readonly StampedeKey _key;
        public ref readonly StampedeKey Key => ref _key;
        protected CacheItem CacheItem => _cacheItem;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="StampedeState"/> class optionally with shared cancellation support.
        /// </summary>
        protected StampedeState(DefaultHybridCache cache, in StampedeKey key, CacheItem cacheItem, bool canBeCanceled)
        {
            _cache = cache;
            _key = key;
            _cacheItem = cacheItem;
            if (canBeCanceled)
            {
                // If the first (or any) caller can't be cancelled;,we'll never get to zero: n point tracking.
                // (in reality, all callers usually use the same path, so cancellation is usually "all" or "none")
                _sharedCancellation = new();
                SharedToken = _sharedCancellation.Token;
            }
            else
            {
                SharedToken = CancellationToken.None;
            }
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="StampedeState"/> class using a fixed cancellation token.
        /// </summary>
        protected StampedeState(DefaultHybridCache cache, in StampedeKey key, CacheItem cacheItem, CancellationToken token)
        {
            _cache = cache;
            _key = key;
            _cacheItem = cacheItem;
            SharedToken = token;
        }
 
#if !NETCOREAPP3_0_OR_GREATER
        protected static readonly WaitCallback SharedWaitCallback = static obj => Unsafe.As<StampedeState>(obj).Execute();
#endif
 
        protected DefaultHybridCache Cache => _cache;
 
        public abstract void Execute();
 
        protected int MaximumPayloadBytes => _cache.MaximumPayloadBytes;
 
        public override string ToString() => Key.ToString();
 
        public abstract void SetCanceled();
 
        public int DebugCallerCount => _cacheItem.RefCount;
 
        public abstract Type Type { get; }
 
        public void CancelCaller()
        {
            // note that TryAddCaller has protections to avoid getting back from zero
            if (_cacheItem.Release())
            {
                // we're the last to leave; turn off the lights
                _sharedCancellation?.Cancel();
                SetCanceled();
            }
        }
 
        public bool TryAddCaller() => _cacheItem.TryReserve();
    }
 
    private void RemoveStampedeState(in StampedeKey key)
    {
        // see notes in SyncLock.cs
        lock (GetPartitionedSyncLock(in key))
        {
            _ = _currentOperations.TryRemove(key, out _);
        }
    }
}