File: System\Text\Json\Serialization\Metadata\ReflectionEmitCachingMemberAccessor.Cache.cs
Web Access
Project: src\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