|
// 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
|