ZiggyCreatures / FusionCache

FusionCache is an easy to use, fast and robust hybrid cache with advanced resiliency features.
MIT License
1.56k stars 84 forks source link

[BUG] FailSafe max duration is somehow ignored #229

Closed sabbadino closed 3 months ago

sabbadino commented 3 months ago

Describe the bug

given these settings "Duration": "00:00:15", "IsFailSafeEnabled": true, "FailSafeMaxDuration": "00:01:00", "FailSafeThrottleDuration": "00:00:01"

using redis for distriubed cache and backplan

builder.Services.AddFusionCache().WithDefaultEntryOptions(fusionCacheSettingConfig.DefaultFusionCacheEntryOptions)
     // ADD JSON.NET BASED SERIALIZATION FOR FUSION CACHE
     .WithSerializer(
         new FusionCacheSystemTextJsonSerializer()
     )
     // ADD REDIS DISTRIBUTED CACHE SUPPORT
     .WithDistributedCache(
         new RedisCache(new RedisCacheOptions {  InstanceName= "fusionCacheApi", Configuration = builder.Configuration["redis:connectionString"] })
     )  // ADD THE FUSION CACHE BACKPLANE FOR REDIS
     .WithBackplane(
         new RedisBackplane(new RedisBackplaneOptions() { Configuration = builder.Configuration["redis:connectionString"] })
     );

I see the entry with the correct expiration ( the one of fail safe) in redis ..

as long has the call fail within the FailSafeMaxDuration i see in the logs ZiggyCreatures.Caching.Fusion.FusionCache: Warning: FUSION [N=FusionCache I=39f93b2ecaf94426aae009f767fbc7b2] (O=0HN2P9TVTIJ21 K=a): FAIL-SAFE activated (from distributed)

When the 1 minute is elapsed , the entry disappear from redis .. but i don't get an error .. message become ZiggyCreatures.Caching.Fusion.FusionCache: Warning: FUSION [N=FusionCache I=39f93b2ecaf94426aae009f767fbc7b2] (O=0HN2P9TVTIJ32 K=a): FAIL-SAFE activated (from memory)

the call start to fail later .. something like 2 minute or so

Expected behavior

I expect to get an error after FailSafeMaxDuration has passed

Versions

I've encountered this issue on:

jodydonetti commented 3 months ago

Hi @sabbadino , may this be a duplicate of this?

(I am already working on it)

sabbadino commented 3 months ago

yes it could be ,, , the exception is thrown eventually but not after expected time lapse (in my case it seems something like between 2 or 3 minutes) while it should be 1 minute as per configuration

sabbadino commented 3 months ago

@jodydonetti , looks like for memory cache ExpirationScanFrequency default is 1 minute .. this would explain why i still see around for some minutes the value after the desired expiration ..

however setting

builder.Services.AddMemoryCache(o => { o.ExpirationScanFrequency = TimeSpan.FromSeconds(1); }); did not make any difference

note that i am running in release mode

sabbadino commented 3 months ago

still not sure .. it seems IMeMoryCache check for expiration on get

 internal MemoryCacheEntry Get(MemoryCacheKey key) {
        MemoryCacheEntry entry = _entries[key] as MemoryCacheEntry;
        // has it expired?
        if (entry != null && entry.UtcAbsExp <= DateTime.UtcNow) {
            Remove(key, entry, CacheEntryRemovedReason.Expired);
            entry = null;
        }
        // update outside of lock
        UpdateExpAndUsage(entry);
        return entry;
    }
jodydonetti commented 3 months ago

Hi, thanks for your time! Anyway I think it's not that: scan frequency is used internally by MemoryCache to free memory, but expiration is always checked nonetheless, logically.

I think I know what the problem is, I just need some more time to better check it and fix it.

Will update asap.

jodydonetti commented 3 months ago

Update: ok I think I've found the culprit, working on the best way to make the change considering all the edge cases.

jodydonetti commented 3 months ago

Hi, I just release v1.1.0 🥳