Skip to content

[BUG] Distributed cache entry removed after expire tag even when fail safe is enabled #553

@nikcio

Description

@nikcio

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.SqlServer 9.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);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions