From e82c7675b451209139ad1d8f75e48da7c951a626 Mon Sep 17 00:00:00 2001 From: Nikolay Zdravkov Date: Wed, 6 May 2026 02:43:00 +0300 Subject: [PATCH] fix: use Stopwatch for ReflectionEmitCachingMemberAccessor TTL --- .../src/System.Text.Json.csproj | 1 + ...flectionEmitCachingMemberAccessor.Cache.cs | 32 +++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 2f1a903683e201..6b79836de8cc60 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -372,6 +372,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitCachingMemberAccessor.Cache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitCachingMemberAccessor.Cache.cs index dc203854c3d858..c924f7de362e68 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitCachingMemberAccessor.Cache.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitCachingMemberAccessor.Cache.cs @@ -14,16 +14,16 @@ internal sealed partial class ReflectionEmitCachingMemberAccessor private sealed class Cache 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 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 _cache = new(); public Cache(TimeSpan slidingExpiration, TimeSpan evictionInterval) { - _slidingExpirationTicks = slidingExpiration.Ticks; - _evictionIntervalTicks = evictionInterval.Ticks; - _lastEvictedTicks = DateTime.UtcNow.Ticks; + _slidingExpiration = slidingExpiration; + _evictionInterval = evictionInterval; + _lastEvictedTimestamp = Stopwatch.GetTimestamp(); } public TValue GetOrAdd(TKey key, Func valueFactory) where TValue : class? @@ -36,17 +36,17 @@ public TValue GetOrAdd(TKey key, Func valueFactory) where #else key => new(valueFactory(key))); #endif - long utcNowTicks = DateTime.UtcNow.Ticks; - Volatile.Write(ref entry.LastUsedTicks, utcNowTicks); + long nowTimestamp = Stopwatch.GetTimestamp(); + Volatile.Write(ref entry.LastUsedTimestamp, nowTimestamp); - if (utcNowTicks - Volatile.Read(ref _lastEvictedTicks) >= _evictionIntervalTicks) + if (Stopwatch.GetElapsedTime(Volatile.Read(ref _lastEvictedTimestamp), nowTimestamp) >= _evictionInterval) { if (Interlocked.CompareExchange(ref _evictLock, 1, 0) == 0) { - if (utcNowTicks - _lastEvictedTicks >= _evictionIntervalTicks) + if (Stopwatch.GetElapsedTime(_lastEvictedTimestamp, nowTimestamp) >= _evictionInterval) { - EvictStaleCacheEntries(utcNowTicks); - Volatile.Write(ref _lastEvictedTicks, utcNowTicks); + EvictStaleCacheEntries(nowTimestamp); + Volatile.Write(ref _lastEvictedTimestamp, nowTimestamp); } Volatile.Write(ref _evictLock, 0); @@ -59,14 +59,14 @@ public TValue GetOrAdd(TKey key, Func valueFactory) where public void Clear() { _cache.Clear(); - _lastEvictedTicks = DateTime.UtcNow.Ticks; + _lastEvictedTimestamp = Stopwatch.GetTimestamp(); } - private void EvictStaleCacheEntries(long utcNowTicks) + private void EvictStaleCacheEntries(long nowTimestamp) { foreach (KeyValuePair kvp in _cache) { - if (utcNowTicks - Volatile.Read(ref kvp.Value.LastUsedTicks) >= _slidingExpirationTicks) + if (Stopwatch.GetElapsedTime(Volatile.Read(ref kvp.Value.LastUsedTimestamp), nowTimestamp) >= _slidingExpiration) { _cache.TryRemove(kvp.Key, out _); } @@ -76,7 +76,7 @@ private void EvictStaleCacheEntries(long utcNowTicks) private sealed class CacheEntry { public readonly object? Value; - public long LastUsedTicks; + public long LastUsedTimestamp; public CacheEntry(object? value) {