-
Notifications
You must be signed in to change notification settings - Fork 167
Description
Describe the bug
It doesn't look like RemoveByTagAsync is removing entries. I'll explain more below.
To Reproduce
In this example (ran in tests/ZiggyCreatures.FusionCache.Playground), I use the idea of users and "teams". This is a very abstract overview of a REST API where users get added to teams, and the results are cached. If this is not clear, let me know and I can write something that's a bit more "real".
Here, we want to cache user 1's data. The response object would be something like:
{
"name": "John Doe",
"teams": [
{
"id": 1,
"name": "John's Team"
}
]
}As a result, if any of John's teams update, we need to clear the cache for the /users/1 endpoint or else we'll be serving stale data. I use the tagging system to "tag" John with his team, so that whenever his team's cache is invalidated his should be also. It doesn't seem to be working however.
public static class ScratchpadScenario
{
public static async Task RunAsync()
{
Console.Title = "FusionCache - Scratchpad";
Console.OutputEncoding = Encoding.UTF8;
var services = new ServiceCollection();
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
services.AddLogging(configure => configure.AddSerilog());
//
var serviceProvider = services.BuildServiceProvider();
// CACHE OPTIONS
var options = new FusionCacheOptions
{
CacheKeyPrefix = "MyCachePrefix:",
DefaultEntryOptions = new FusionCacheEntryOptions
{
Duration = TimeSpan.FromMinutes(1),
},
};
var logger = serviceProvider.GetRequiredService<Microsoft.Extensions.Logging.ILogger<FusionCache>>();
var cache = new FusionCache(options);
var distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()));
var serializer = new FusionCacheNewtonsoftJsonSerializer();
cache.SetupDistributedCache(distributedCache, serializer);
var backplane = new MemoryBackplane(new MemoryBackplaneOptions());
cache.SetupBackplane(backplane);
var key = "baz";
Console.WriteLine("GET /user/1");
var tag = "user:1"; // no team tied to user
var val = await cache.GetOrSetAsync(key, async ct => await GetUserAsync(), tags: [tag]);
Console.WriteLine("GET /user/1");
val = await cache.GetOrSetAsync(key, async ct => await GetUserAsync(), tags: [tag]);
Console.WriteLine("POST /teams/1/users");
await cache.RemoveByTagAsync(tag);
Console.WriteLine("GET /user/1");
var teamTag = "team:1"; // team tied to user
val = await cache.GetOrSetAsync(key, async ct => await GetUserAsync(), tags: [tag, teamTag]);
Console.WriteLine("PUT /teams/1");
await cache.RemoveByTagAsync(teamTag);
Console.WriteLine("GET /user/1");
val = await cache.GetOrSetAsync(key, async ct => await GetUserAsync(), tags: [tag, teamTag]);
Console.WriteLine("DELETE /teams/1/users/1");
await cache.RemoveByTagAsync(teamTag);
Console.WriteLine("GET /user/1");
val = await cache.GetOrSetAsync(key, async ct => await GetUserAsync(), tags: [tag]); // no team tag as it was removed
Console.WriteLine("PUT /teams/1");
await cache.RemoveByTagAsync(teamTag);
Console.WriteLine("GET /user/1");
val = await cache.GetOrSetAsync(key, async ct => await GetUserAsync(), tags: [tag]); // same as above, no team tag
}
private static async Task<string> GetUserAsync()
{
Console.WriteLine("Getting user...");
// simulate some work
await Task.Delay(2000).ConfigureAwait(false);
// return the value
return "qux"; // can be anything
}
}Console output is:
GET /user/1
Getting user...
GET /user/1
POST /teams/1/users
GET /user/1
Getting user...
PUT /teams/1
GET /user/1
DELETE /teams/1/users/1
GET /user/1
PUT /teams/1
GET /user/1
Expected behavior
In the example above, we hit the "database" (GetUserAsync()) twice, but it should have been 4 times:
- The first
GET /user/1✅ - The third
GET /user/1, just after wePOST /teams/1/usersas the user has a new team ✅ - The fourth
GET /user/1after we update the team we are now a part of ❌ - The fifth
GET /user/1after we remove ourselves from the team ❌
Also, the final GET /user/1 should not force a cache miss, as we are no longer part of that team - we shouldn't have that tag anymore.
Versions
I've encountered this issue on:
mainbranch (ran my test on the playground onmain)- .NET 8.0.18
- MacOS 15.5 (24F74)
Additional context
If I've missed anything please let me know!!