From 5137b68768fcacc2fd96765b6ea574e93927be9b Mon Sep 17 00:00:00 2001 From: Reza Heidari Date: Wed, 3 Jun 2026 16:15:37 +0300 Subject: [PATCH] Evict corrupt distributed cache entries --- .../Adapters/DistributedCacheAdapter.cs | 26 +++++++++++++++ .../DistributedCacheAdapterTests.cs | 32 +++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/CleanArchitecture.Extensions.Caching/Adapters/DistributedCacheAdapter.cs b/src/CleanArchitecture.Extensions.Caching/Adapters/DistributedCacheAdapter.cs index a4ff385..e019a18 100644 --- a/src/CleanArchitecture.Extensions.Caching/Adapters/DistributedCacheAdapter.cs +++ b/src/CleanArchitecture.Extensions.Caching/Adapters/DistributedCacheAdapter.cs @@ -80,6 +80,7 @@ public DistributedCacheAdapter( var stored = DeserializeStored(bytes); if (stored is null) { + RemoveInvalidEntry(key); return null; } @@ -104,6 +105,7 @@ public DistributedCacheAdapter( var stored = DeserializeStored(bytes); if (stored is null) { + await RemoveInvalidEntryAsync(key, cancellationToken).ConfigureAwait(false); return null; } @@ -308,6 +310,30 @@ private DistributedStoredEntry CreateEnvelope(T value, CacheEntryOptions o } } + private void RemoveInvalidEntry(CacheKey key) + { + try + { + _distributedCache.Remove(key.FullKey); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to remove invalid distributed cache entry for {Key}", key.FullKey); + } + } + + private async Task RemoveInvalidEntryAsync(CacheKey key, CancellationToken cancellationToken) + { + try + { + await _distributedCache.RemoveAsync(key.FullKey, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to remove invalid distributed cache entry for {Key}", key.FullKey); + } + } + private static TimeSpan? ApplyJitter(TimeSpan? baseValue, TimeSpan? jitter) { if (!baseValue.HasValue) diff --git a/tests/CleanArchitecture.Extensions.Caching.Tests/DistributedCacheAdapterTests.cs b/tests/CleanArchitecture.Extensions.Caching.Tests/DistributedCacheAdapterTests.cs index 6cda383..1fd701d 100644 --- a/tests/CleanArchitecture.Extensions.Caching.Tests/DistributedCacheAdapterTests.cs +++ b/tests/CleanArchitecture.Extensions.Caching.Tests/DistributedCacheAdapterTests.cs @@ -13,10 +13,10 @@ namespace CleanArchitecture.Extensions.Caching.Tests; public class DistributedCacheAdapterTests { - private static DistributedCacheAdapter CreateAdapter(CachingOptions? options = null, ICacheSerializer? serializer = null) + private static DistributedCacheAdapter CreateAdapter(CachingOptions? options = null, ICacheSerializer? serializer = null, IDistributedCache? distributed = null) { serializer ??= new SystemTextJsonCacheSerializer(); - var distributed = new MemoryDistributedCache(MOptions.Create(new MemoryDistributedCacheOptions())); + distributed ??= new MemoryDistributedCache(MOptions.Create(new MemoryDistributedCacheOptions())); var cachingOptions = options ?? CachingOptions.Default; return new DistributedCacheAdapter( distributed, @@ -86,6 +86,34 @@ public void Skips_storing_when_payload_exceeds_limit() Assert.Null(adapter.Get(key)); } + [Fact] + public void Get_removes_corrupt_payload() + { + var distributed = new MemoryDistributedCache(MOptions.Create(new MemoryDistributedCacheOptions())); + var adapter = CreateAdapter(distributed: distributed); + var key = new CacheKey("ns", "Resource", "corrupt-sync"); + distributed.Set(key.FullKey, "not-json"u8.ToArray(), new DistributedCacheEntryOptions()); + + var cached = adapter.Get(key); + + Assert.Null(cached); + Assert.Null(distributed.Get(key.FullKey)); + } + + [Fact] + public async Task GetAsync_removes_corrupt_payload() + { + var distributed = new MemoryDistributedCache(MOptions.Create(new MemoryDistributedCacheOptions())); + var adapter = CreateAdapter(distributed: distributed); + var key = new CacheKey("ns", "Resource", "corrupt-async"); + await distributed.SetAsync(key.FullKey, "not-json"u8.ToArray(), new DistributedCacheEntryOptions()); + + var cached = await adapter.GetAsync(key); + + Assert.Null(cached); + Assert.Null(await distributed.GetAsync(key.FullKey)); + } + [Fact] public async Task Uses_serializer_content_type() {