File: System\Text\Json\Serialization\Metadata\ReflectionEmitCachingMemberAccessor.Cache.cs
Web Access
Project: src\src\runtime\src\libraries\System.Text.Json\src\System.Text.Json.csproj (System.Text.Json)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NETFRAMEWORK || NET
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace System.Text.Json.Serialization.Metadata
{
    internal sealed partial class ReflectionEmitCachingMemberAccessor
    {
        private sealed class Cache<TKey> where TKey : notnull
        {
            private int _evictLock;
            private long _lastEvictedTicks; // timestamp of latest eviction operation.
            private readonly long _evictionIntervalTicks; // min timespan needed to trigger a new evict operation.
            private readonly long _slidingExpirationTicks; // max timespan allowed for cache entries to remain inactive.
            private readonly ConcurrentDictionary<TKey, CacheEntry> _cache = new();

            public Cache(TimeSpan slidingExpiration, TimeSpan evictionInterval)
            {
                _slidingExpirationTicks = slidingExpiration.Ticks;
                _evictionIntervalTicks = evictionInterval.Ticks;
                _lastEvictedTicks = DateTime.UtcNow.Ticks;
            }

            public TValue GetOrAdd<TValue>(TKey key, Func<TKey, TValue> valueFactory) where TValue : class?
            {
                CacheEntry entry = _cache.GetOrAdd(
                    key,
#if NET
                    static (TKey key, Func<TKey, TValue> valueFactory) => new(valueFactory(key)),
                    valueFactory);
#else
                    key => new(valueFactory(key)));
#endif
                long utcNowTicks = DateTime.UtcNow.Ticks;
                Volatile.Write(ref entry.LastUsedTicks, utcNowTicks);

                if (utcNowTicks - Volatile.Read(ref _lastEvictedTicks) >= _evictionIntervalTicks)
                {
                    if (Interlocked.CompareExchange(ref _evictLock, 1, 0) == 0)
                    {
                        if (utcNowTicks - _lastEvictedTicks >= _evictionIntervalTicks)
                        {
                            EvictStaleCacheEntries(utcNowTicks);
                            Volatile.Write(ref _lastEvictedTicks, utcNowTicks);
                        }

                        Volatile.Write(ref _evictLock, 0);
                    }
                }

                return (TValue)entry.Value!;
            }

            public void Clear()
            {
                _cache.Clear();
                _lastEvictedTicks = DateTime.UtcNow.Ticks;
            }

            private void EvictStaleCacheEntries(long utcNowTicks)
            {
                foreach (KeyValuePair<TKey, CacheEntry> kvp in _cache)
                {
                    if (utcNowTicks - Volatile.Read(ref kvp.Value.LastUsedTicks) >= _slidingExpirationTicks)
                    {
                        _cache.TryRemove(kvp.Key, out _);
                    }
                }
            }

            private sealed class CacheEntry
            {
                public readonly object? Value;
                public long LastUsedTicks;

                public CacheEntry(object? value)
                {
                    Value = value;
                }
            }
        }
    }
}
#endif