File: System\Text\Json\Serialization\Metadata\ReflectionEmitCachingMemberAccessor.Cache.cs
Web Access
Project: 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 _lastEvictedTimestamp; // Stopwatch timestamp of the latest eviction operation.
            private readonly TimeSpan _evictionInterval; // min duration needed to trigger a new evict operation.
            private readonly TimeSpan _slidingExpiration; // max duration allowed for cache entries to remain inactive.
            private readonly ConcurrentDictionary<TKey, CacheEntry> _cache = new();

            public Cache(TimeSpan slidingExpiration, TimeSpan evictionInterval)
            {
                _slidingExpiration = slidingExpiration;
                _evictionInterval = evictionInterval;
                _lastEvictedTimestamp = Stopwatch.GetTimestamp();
            }

            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 nowTimestamp = Stopwatch.GetTimestamp();
                Volatile.Write(ref entry.LastUsedTimestamp, nowTimestamp);

                if (Stopwatch.GetElapsedTime(Volatile.Read(ref _lastEvictedTimestamp), nowTimestamp) >= _evictionInterval)
                {
                    if (Interlocked.CompareExchange(ref _evictLock, 1, 0) == 0)
                    {
                        if (Stopwatch.GetElapsedTime(Volatile.Read(ref _lastEvictedTimestamp), nowTimestamp) >= _evictionInterval)
                        {
                            EvictStaleCacheEntries(nowTimestamp);
                            Volatile.Write(ref _lastEvictedTimestamp, nowTimestamp);
                        }

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

                return (TValue)entry.Value!;
            }

            public void Clear()
            {
                _cache.Clear();
                Volatile.Write(ref _lastEvictedTimestamp, Stopwatch.GetTimestamp());
            }

            private void EvictStaleCacheEntries(long nowTimestamp)
            {
                foreach (KeyValuePair<TKey, CacheEntry> kvp in _cache)
                {
                    if (Stopwatch.GetElapsedTime(Volatile.Read(ref kvp.Value.LastUsedTimestamp), nowTimestamp) >= _slidingExpiration)
                    {
                        _cache.TryRemove(kvp.Key, out _);
                    }
                }
            }

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

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