-
Notifications
You must be signed in to change notification settings - Fork 174
Description
Describe the bug
When having fail-safe activated, using distributed cache and using tag expiration a call to TryGetAsync will clear the value from the distributed cache even when fail-safe is enabled.
I noticed that this isn't the case when just using GetOrSetAsync so therefore think it's a bug.
When hosting multiple nodes where the memory cache duration is short (and no backplane) removing the distributed cache value means that other instances now have to fetch the value again before being able to return the value which also means failing factories will bubble out to the user instead of using a fail-safe value.
As far as I understand the Fail-safe should always be present until the entry expires due the FailSafeMaxDuration or DistributedFailSafeMaxDuration is exceeded?
To Reproduce
Here's a MRE (Minimal Reproducible Example) of the issue:
https://github.com/nikcio/fusion-cache-distributed-cache-entry-removed
In the Repository above I've create a small API project where you can see this behavior. In the README there's a guide to setup and run the test.
Expected behavior
I would expect the value in the distributed cache to not be deleted when fail-safe is activated for the entry.
Versions
I've encountered this issue on:
- FusionCache version 2.4.0
- .NET version 9.0.0
- OS version Windows 11
- SQL server 2022 (
Microsoft.Extensions.Caching.SqlServer9.0.10)
Screenshots
If applicable, add screenshots to help explain your problem.
Additional context
I've looked a bit in the source code and think the behavior comes from this section:
| private ValueTask DistributedExpireEntryAsync(string operationId, string key, FusionCacheEntryOptions options, CancellationToken token) |
Called from:
TryGetEntryInternalAsync
| private async ValueTask<IFusionCacheMemoryEntry?> TryGetEntryInternalAsync<TValue>(string operationId, string key, FusionCacheEntryOptions options, Activity? activity, CancellationToken token) |
| (memoryEntry, memoryEntryIsValid) = await CheckEntrySecondaryExpirationAsync(operationId, key, memoryEntry, true, token).ConfigureAwait(false); |
CheckEntrySecondaryExpirationAsync
| private async ValueTask<(TEntry? Entry, bool isValid)> CheckEntrySecondaryExpirationAsync<TEntry>(string operationId, string key, TEntry? entry, bool executeCascadeAction, CancellationToken token) |
| await ExpireInternalAsync(key, _cascadeRemoveByTagEntryOptions, token).ConfigureAwait(false); |
ExpireInternalAsync
| private async ValueTask ExpireInternalAsync(string key, FusionCacheEntryOptions options, CancellationToken token = default) |
| await DistributedExpireEntryAsync(operationId, key, options, token).ConfigureAwait(false); |