Skip to content

[deps]: Update fusioncache monorepo to v2.9.0 - #6796

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/fusioncache-monorepo
Open

renovate[bot] wants to merge 1 commit into
mainfrom
renovate/fusioncache-monorepo

Conversation

@renovate

@renovate renovate Bot commented Jan 5, 2026 •

Copy link
Copy Markdown
Contributor

Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
ZiggyCreatures.FusionCache [2.0.2] → [2.9.0] age confidence
ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis [2.0.2] → [2.9.0] age confidence
ZiggyCreatures.FusionCache.Serialization.SystemTextJson [2.0.2] → [2.9.0] age confidence

Release Notes

ZiggyCreatures/FusionCache (ZiggyCreatures.FusionCache)

v2.9.0

🦅 Eager Refresh now also checks L2

Community member @​sfhb24 noticed that during an Eager Refresh the L2 was not being checked.

Now, this is admittedly not a huge thing per se, because of 2 reasons:

  • backplane: when using an L1+L2 setup, a backplane is usually also used, and in that case the factory would not run because an update on L2 would be immediately visible to the other nodes
  • distributed locker: by using a distributed locker a factory would not run because nodes would coordinate so that only 1 factory runs concurrently, even on multiple nodes

In both these cases, the extra L2 check during an eager refresh would not be necessary.

Having said that, an L1+L2 setup without a backplane or a distributed locker may also be common, and in that case the new L2 check in the eager refresh window can in fact be helpful in reducing the amount of factory executions even more.

Long story short: I just implemented it!

See here for the issue.

🔒 Fix for memory locker + Eager Refresh edge case

If a factory fails when executed in the background during an eager refresh, FusionCache already takes care of everything and correctly releases the potentially acquired locks (memory and/or distributed), and this is good.

But community member @​joaopbnogueira noticed a peculiar edge case: if the factory is not one marked with the async keyword AND it throws an exception, the memory lock is not being released properly.

Or, to better say, "was not". Because now this has been fixed.

Thanks João for spotting this.

See here for the issue.

🔒 Fix for distributed locker + skip L1 edge case

Community member @​joaopbnogueira also noticed another peculiar scenario, specifically when a MemoryCache write fails (yep, it can happen, true story).

Here's an example of it:

  • a custom MemroyCache is being used as the L1
  • that instance has been configured with a SizeLimit
  • there's a cache miss
  • the factory executes successfully
  • the new entry does not have a Size specified
  • L1 write fails (because with a SizeLimit it's mandatory for every entry to specify a Size)

In this scenario a distributed lock may not have been released.

But fear no more: this does not hapen anymore!

See here for the issue.

🧼 Fix for Clear(false) + Fail-Safe

Community member @​joaopbnogueira (damn, he is on a roll!) noticed something else, that somehow went unnoticed until now.

In some cases, after a Clear(false) call, an expired entry saved with Fail-Safe enabled may still be returned by FusionCache.

This too has now been fixed.

See here for the issue.

🔭 Allocation-free tags on the metrics hot path

Community member @​GordeySt sent a PR with some really nice optimizations, particularly around reducing resource allocations and CPU consumption when working with metrics.

Now, even with metrics enabled, the hot path is allocation free.

That's great, thanks Gordey!

See here for the PR.

✅ Tests

The test suite just crossed the 1600 mark:

image

Nice 🙂

v2.8.0

🔒 Fix for distributed lock release when skipping L2 write

Community member @​joaopbnogueira spotted a problem with an edge case: when using SkipDistributedCacheWrite the distributed locker was not being properly disposed, which was unfortunate.
Now this has been fixed.

See here for the issue.

🏷️ Fix for tag marker re-materialization

Community member @​igor-henriques noticed an issues because of which when the tag marker (the special cache entry containing the RemoveByTag() timestamp) expired, it was being re-materialized with a newer timestamp: this could have led to the same results as a new RemoveByTag() call.
That was unfortunate, but it has now been fixed.

See here for the issue.

🏷️ Better handling of RemoveByTagBehavior.Remove

Community member @​gpetrou asked for some help regarding a certain scenario, and during the explanation/investigation it emerged that FusionCache could have handled RemoveByTagBehavior.Remove in a slightly better way.
It was mostly an edge case, but still: now the way it is internally handled is even better than before.

See here for the issue.

🔭 Better observability for user-initiated cancellations

Community member @​dzmitry-tsarevich highlighted that user-initiated cancellations were being handled in a little-too-aggressive way from the oint of view of observability: too much background noise was being generated, which could lead to bloat.
Now this has been made better, slimmer.

See here for the issue.

👷 New builder ext methods

Community member @​Stepami noticed the lack of a specific ext method on the builder to register the distributed locker based on Redis, basically WithRedisDistributedLocker().

After accepting his PR, I noticed a couple of extra ones were also missing, and so I added them.

See here for the issue.

Also, community member @​petriceko asked for a new overload of the WithOptions() ext method with better DI support: promptly, community member @​vrbyjimmy made a PR to add that, which I merged.
Talk about community collaboration 🙂

See here for the issue.

v2.7.2

🔃 Better DI registrations checks

Community member @​erikatsg noticed what seemed like a strange behavior, but after a preliminary check discovered that the problem was on their side, because they were adding the same FusionCache registration to the DI container more than once.

Something like this:

services.AddFusionCache()
  .WithDefaultEntryOptions(options =>
  {
    options.Duration = TimeSpan.FromSeconds(111);
  });

// LATER...

services.AddFusionCache()
  .WithDefaultEntryOptions(options =>
  {
    options.Duration = TimeSpan.FromSeconds(222);
  });

This is generally not suggested nor supported.

Luckily, FusionCache already had a series of checks for that and more strange situations, and those checks emit log records in those situations.

Unluckily though, those checks were previously performed only when asking to the DI container for an IFusionCacheProvider (to work with named caches).

Well, not anymore: now it works in any setup/scenarion, all automatically.

The current list of checks performed is:

  • multiple direct IFusionCache registrations (e.g.: multiple services.AddFusionCache() calls)
  • multiple named IFusionCache registrations (e.g.: multiple services.AddFusionCache(name) calls with the same name)
  • multiple keyed IFusionCache registrations (e.g.: multiple services.AddFusionCache().AsKeyedService(key) calls with the same key, for either named caches or the default cache)

See here for the issue.

🏅 Better Advisor checks

A new check has been added in the Advisor that detecs a potentially incorrect configuration for a System.Text.Json based serializer related to value tuples.

This is not about a FusionCache bug, but about the System.Text.Json serializer's default configuration deviating from the commonly expected one (like, historically, Json.NET for example), see here for more.

Instead of forcing a specific json configuration for FusionCache, the Advisor now checks if the serializer in use is not supporting value tuples correctly and, if so, emits a warning (also configurable) in the logs to warn users about that.

This allows them to make the best decision for their specific use case.

Thanks to @​sychare and @​bassepeder for the hints.

Also, the check for a missing CacheKeyPrefix is now better, more limited in scope.

See here and here for the issues.

🆕 New SerializationConfigIssuesLogLevel option

See the previous item: with this new option is possible to change the desired log level, or suppress it.

🆕 New DistributedLockerErrorsLogLevel option

It's now possible to granularly configure the log level to use for distributed lockers errors, which is nice.

🔭 Better handling of OTEL parent traces

Sometimes the handling of OTEL parent traces was not the best, particularly around highly multithreaded scenarios where a native Activity is not being passed around correctly in the context.

This has now been fixed.

🏷️ OTEL traces for a RemoveByTag(tag) operation now include the tag (duh)

Normally, every operation in the cache has a cache key as the main argument.

In the case of a RemoveByTag(tag) operation though, that is not the case since the main argument is not the cache key, but the tag: strangely enough, FusionCache previously was not logging it front and center, but now it does.

This should help with tagging-related investigations and troubleshootings.

Thanks to community member @​tvardero for spotting this.

See here for the issue.

🐞 Better temporal checks during an L2 to L1 copy

Thanks to community member @​DanielStout5 for spotting this: it's quite rare, but sometimes an entry in L2 may be older than the corresponding entry in L1, and before this fix that assumption did not hold sometimes.
Now an extra check is performed, to make sure that this scenario doesnot produce unwanted results.

See here for the issue.

🐞 Missing operation id with tracing but without a logger

Every single operation in FusionCache has a uniquely identifying operation id (OperationId): this becomes very useful with observability, mainly with logs.

Previously, when no logger was present, the operation id was not generated, at all: this was an optimization to avoid useless work when not actually needed.

But in a case of traces being used without a logger, the traces did not include the operation id for the reason explain above.

With this fix, the optimization is still there, but it also takes into account trace listeners, so that when either a logger or a trace listener is present the operation id is generated, otherwise the optimization is still there.

🔒 Better exception handling for user-initiated cancellations with distributed locking

Community member @​vrbyjimmy discovered a nasty issue with user-initiated cancellations when used with the distributed locker, see the issue for more details.

Than, community member @​qjustfeelitp sent a PR to fix exactly this: teamwork doing its thing, amirite?

Long story short: now that specific OperationCanceledException is being specifically handled to avoid creating issues.

Thanks both!

See here for the issue.

⚠️ Dependencies update to fix in vulnerability in downstream dependencies

Thanks to community member @​SirPustekuchen for noticing that the MessagePack package version being normally used was since marked as vulnerable: now the reference has been updated.

And thanks to communiy membe @​aki-matleb for doing the same, but for the OpenTelemetry.Api package.

See here and here for the issues.

📕 Docs

As always, I took some time to updated the docs with the latest stuff and make them overall better.

v2.7.1

[!NOTE]
You should update to v2.7.2, see here.

🏅 Better Advisor checks

A new check has been added in the Advisor that detecs a potentially incorrect configuration for a System.Text.Json based serializer related to value tuples.

This is not about a FusionCache bug, but about the System.Text.Json serializer's default configuration deviating from the commonly expected one (like, historically, Json.NET for example), see here for more.

Instead of forcing a specific json configuration for FusionCache, the Advisor now checks if the serializer in use is not supporting value tuples correctly and, if so, emits a warning (also configurable) in the logs to warn users about that.

This allows them to make the best decision for their specific use case.

Thanks to @​sychare and @​bassepeder for the hints.

Also, the check for a missing CacheKeyPrefix is now better, more limited in scope.

See here and here for the issues.

🆕 New SerializationConfigIssuesLogLevel option

See the previous item: with this new option is possible to change the desired log level, or suppress it.

🆕 New DistributedLockerErrorsLogLevel option

It's now possible to granularly configure the log level to use for distributed lockers errors, which is nice.

🔭 Better handling of OTEL parent traces

Sometimes the handling of OTEL parent traces was not the best, particularly around highly multithreaded scenarios where a native Activity is not being passed around correctly in the context.

This has now been fixed.

🏷️ OTEL traces for a RemoveByTag(tag) operation now include the tag (duh)

Normally, every operation in the cache has a cache key as the main argument.

In the case of a RemoveByTag(tag) operation though, that is not the case since the main argument is not the cache key, but the tag: strangely enough, FusionCache previously was not logging it front and center, but now it does.

This should help with tagging-related investigations and troubleshootings.

Thanks to community member @​tvardero for spotting this.

See here for the issue.

🐞 Better temporal checks during an L2 to L1 copy

Thanks to community member @​DanielStout5 for spotting this: it's quite rare, but sometimes an entry in L2 may be older than the corresponding entry in L1, and before this fix that assumption did not hold sometimes.
Now an extra check is performed, to make sure that this scenario doesnot produce unwanted results.

See here for the issue.

🐞 Missing operation id with tracing but without a logger

Every single operation in FusionCache has a uniquely identifying operation id (OperationId): this becomes very useful with observability, mainly with logs.

Previously, when no logger was present, the operation id was not generated, at all: this was an optimization to avoid useless work when not actually needed.

But in a case of traces being used without a logger, the traces did not include the operation id for the reason explain above.

With this fix, the optimization is still there, but it also takes into account trace listeners, so that when either a logger or a trace listener is present the operation id is generated, otherwise the optimization is still there.

🔒 Better exception handling for user-initiated cancellations with distributed locking

Community member @​vrbyjimmy discovered a nasty issue with user-initiated cancellations when used with the distributed locker, see the issue for more details.

Than, community member @​qjustfeelitp sent a PR to fix exactly this: teamwork doing its thing, amirite?

Long story short: now that specific OperationCanceledException is being specifically handled to avoid creating issues.

Thanks both!

See here for the issue.

⚠️ Dependencies update to fix in vulnerability in downstream dependencies

Thanks to community member @​SirPustekuchen for noticing that the MessagePack package version being normally used was since marked as vulnerable: now the reference has been updated.

And thanks to communiy membe @​aki-matleb for doing the same, but for the OpenTelemetry.Api package.

See here and here for the issues.

📕 Docs

As always, I took some time to updated the docs with the latest stuff and make them overall better.

v2.7.0

[!NOTE]
You should update to v2.7.2, see here: there's a small issue in v2.7.0, nothing critical really, but better to use the new version.

🏅 Better Advisor checks

A new check has been added in the Advisor that detecs a potentially incorrect configuration for a System.Text.Json based serializer related to value tuples.

This is not about a FusionCache bug, but about the System.Text.Json serializer's default behavior deviating from the commonly expected one (like, historically, Json.NET for example), see here for more.

Instead of forcing different json options specifically for FusionCache, the Advisor now checks if the serializer in use is not supporting value tuples correctly and, if so, emits a warning (also configurable) in the logs to warn users about that.

This allows them to make the best decision for their specific use case.

Thanks to @​sychare and @​bassepeder for the hints.

Also, the check for a missing CacheKeyPrefix is now better, more limited in scope.

See here and here for the issues.

🆕 New SerializationIssuesLogLevel option

See the previous item: with this new option is possible to change the desired log level, or suppress it.

🆕 New DistributedLockerErrorsLogLevel option

It's now possible to granularly configure the log level to use for distributed lockers errors, which is nice.

🔭 Better handling of OTEL parent traces

Sometimes the handling of OTEL parent traces was not the best, particularly around highly multithreaded scenarios where a native Activity is not being passed around correctly in the context.

This has now been fixed.

🏷️ OTEL traces for a RemoveByTag(tag) operation now include the tag (duh)

Normally, every operation in the cache has a cache key as the main argument.

In the case of a RemoveByTag(tag) operation though, that is not the case since the main argument is not the cache key, but the tag: strangely enough, FusionCache previously was not logging it front and center, but now it does.

This should help with tagging-related investigations and troubleshootings.

Thanks to community member @​tvardero for spotting this.

See here for the issue.

🐞 Better temporal checks during an L2 to L1 copy

Thanks to community member @​DanielStout5 for spotting this: it's quite rare, but sometimes an entry in L2 may be older than the corresponding entry in L1, and before this fix that assumption did not hold sometimes.
Now an extra check is performed, to make sure that this scenario doesnot produce unwanted results.

See here for the issue.

🐞 Missing operation id with tracing but without a logger

Every single operation in FusionCache has a uniquely identifying operation id (OperationId): this becomes very useful with observability, mainly with logs.

Previously, when no logger was present, the operation id was not generated, at all: this was an optimization to avoid useless work when not actually needed.

But in a case of traces being used without a logger, the traces did not include the operation id for the reason explain above.

With this fix, the optimization is still there, but it also takes into account trace listeners, so that when either a logger or a trace listener is present the operation id is generated, otherwise the optimization is still there.

🔒 Better exception handling for user-initiated cancellations with distributed locking

Community member @​vrbyjimmy discovered a nasty issue with user-initiated cancellations when used with the distributed locker, see the issue for more details.

Than, community member @​qjustfeelitp sent a PR to fix exactly this: teamwork doing its thing, amirite?

Long story short: now that specific OperationCanceledException is being specifically handled to avoid creating issues.

Thanks both!

See here for the issue.

⚠️ Dependencies update to fix in vulnerability in downstream dependencies

Thanks to community member @​SirPustekuchen for noticing that the MessagePack package version being normally used was since marked as vulnerable: now the reference has been updated.

And thanks to communiy membe @​aki-matleb for doing the same, but for the OpenTelemetry.Api package.

See here and here for the issues.

📕 Docs

As always, I took some time to updated the docs with the latest stuff and make them overall better.

v2.6.0

🏷️ Configurable cleanup behavior for RemoveByTag()

Normally, when calling RemoveByTag("my-tag"), the entries with such a tag will be gradually expired on a subsequent access.

Community member @​charlesvigneault asked for the ability to instead properly remove them.

So I added a new option to allow configuring this behavior:

services.AddFusionCache()
	.WithOptions(options =>
	{
		options.RemoveByTagBehavior = RemoveByTagBehavior.Remove;
	});

See here for the original issue.

Ⓜ️ Add support for RemoveByTag("*") in HybridCache adapter

After the initial release of HybridCache in 2025, the team added support for a special case: using RemoveByTag("*") to clear the entire cache.

I didn't notice untile recently, and thanks to community user @​vrbyjimmy I did that.
Or, to better say it, he did that!
He acted so quickly that a PR immediately landed with the implementation, so thanks Jakub for that!

What happens underneath is that a RemoveByTag("*") call on the adapter is detected and re-routed to a Clear() call on the underlying FusionCache instance: very simple and elegant, and I like that a lot.

See here for the original issue.

🔒 Better Distributed Locker + Eager Refresh

Community user @​jgshowpad noticed that when using the new distributed stampede protection introduced in v2.5.0 with Eager Refresh some errors were being logged.

That was caused by the Redis-based distributed locker not handling correctly a timeout of zero (which btw is a pretty common approach to basically check for a lock already being acquired by someone else, without having to wait).

This has now been fixed.

See here for the original issue.

⚡ Perf boost for GenerateOperationId()

Community user @​Inok contributed with a nice set of low-level perf optimizations for the GenerateOperationId() internal method, which may be called quite a lot when doing observability (logging, OTEL, etc).

That's a very nice and welcome contribution, thanks Pavel!

See here for the original issue.

🔒 Add missing ext method for DI registration of

Community member @​mumin-khan noticed that, after releasing distributed stampede support in v2.5.0, I missed the related ext method for the the DI registration.

So now I added it, and it's now possible to do this:

builder.Services.AddFusionCacheRedisDistributedLocker(options =>
  {
    options.Configuration = ... ;
  });

This has now been added, thanks Mumin!

See here for the original issue.

🐞 Fixed a couple of missing ConfigureAwait(false)

Community user @​JerinMathewJose01 noticed that on the old .NET Framework 4.7.2 and 4.8, sometimes the factory may remain stuck without completing correctly.

That was caused by a couple of missing ConfigureAwait(false) when awaiting the factory execution.

This has now been fixed.

See here for the original issue.

📕 Docs

As always, I took some time to updated the docs with the latest stuff and make them overall better.

v2.5.0

🛡️ Distributed Cache Stampede Protection

Since the very beginning FusionCache offered a solid Cache Stampede protection, as explained in the docs where it is clearly illustrated:

Cache Stampede Request Coalescing

Such protection worked not just in the normal flow (miss -> factory -> return) but also with other more advanced features like:

  • Eager Refresh: hit (after the eager threshold) -> return + background factory
  • Factory Timeouts: miss -> factory + timeout -> return + background complete

With time the stampede protection got even better, and even extensible: this allowed 3rd party implementations of the core mechanism, called memory locker (IFusionCacheMemoryLocker).

All of this without removing the normal "it just works" experience since, by default, a StandardMemoryLocker is used without needing any user setup or intervention.

Cool.

But here's the thing: this protection had always been a local thing, meaning it did not span multiple nodes, in a distributed way: this meant that, if we were "unlucky", multiple factories could have run at the same time for the same cache key on different nodes.

Meaning, this:

Distributed Cache Stampede, Before

But that was true until now: enter Distributed Cache Stampede Protection 🎉

Thanks to the introduction of the new IFusionCacheDistributedLocker (see the next point) it's now possible to coordinate factory execution accross multiple nodes, so that only one factory would run at the same time for the same cache key even on different nodes.

Meaning, this:

Distributed Cache Stampede, After

By providing an IFusionCacheDistributedLocker implementation during setup, FusionCache will take care of everything, we don't have to do anything else.

The setup looks like this:

services.AddFusionCache()
	// SERIALIZER
	.WithSerializer(
		new FusionCacheSystemTextJsonSerializer()
	)
	// DISTRIBUTED CACHE
	.WithDistributedCache(
		new RedisCache(new RedisCacheOptions
		{
			Configuration = "localhost:6379",
		})
	)
	// BACKPLANE
	.WithBackplane(
		new RedisBackplane(new RedisBackplaneOptions
		{
			Configuration = "localhost:6379",
		})
	)
	// DISTRIBUTED LOCKER <-- HERE IT IS!
	.WithDistributedLocker(
		new RedisDistributedLocker(new RedisDistributedLockerOptions
		{
			Configuration = "localhost:6379",
		})
	);

Or, even better, if we want to re-use the same connection multiplexer for better performanceand use of resources, we can do this:

var muxer = ConnectionMultiplexer.Connect("localhost:6379");

services.AddFusionCache()
	// SERIALIZER
	.WithSerializer(
		new FusionCacheSystemTextJsonSerializer()
	)
	// DISTRIBUTED CACHE
	.WithDistributedCache(
		new RedisCache(new RedisCacheOptions
		{
			ConnectionMultiplexerFactory = async () => muxer,
		})
	)
	// BACKPLANE
	.WithBackplane(
		new RedisBackplane(new RedisBackplaneOptions
		{
			ConnectionMultiplexerFactory = async () => muxer,
		})
	)
	// DISTRIBUTED LOCKER <-- HERE IT IS!
	.WithDistributedLocker(
		new RedisDistributedLocker(new RedisDistributedLockerOptions
		{
			ConnectionMultiplexerFactory = async () => muxer,
		})
	);

As always, the idea is that "it just works".

See here for the original issue.

🔒 Extensible Distributed Locking

As mentioned above, this is the new distributed component responsible for coordinating multiple factory executions on different nodes, all automatically.

As of now I'm providing 2 main implementations:

Of course the Redis one is the only real deal for now, meant for production use.

Other implementations in the future will be possible, by simply implementing the new IFusionCacheDistributedLocker abstraction, just like it was possible before with the IFusionCacheMemoryLocker abstraction.

So to recap:

  • install the package (e.g.: the Redis one)
  • add 1 line in the setup (just like for the distributed cache or the backplane)
  • done

I would say it's all pretty nice 🙂

See here for the original issue.

⚙️ New MemoryCacheDuration entry option

This is seemingly small, but really important.

In a multi-node scenario with an L1+L2 setup it's important to keep the cache, as a whole, coherent.

When using a Backplane there's no need to do anything: all is taken care of, and the cache as a whole is always coherent.

But wha if we cannot or don't want to use a backplane, for... reasons?

Well, every change in the cache will leave the other L1s out-of-sync for the remaining time before their expiration, and this is not good.

This problem is known as Cache Coherence, and the backplane is what is used to SOLVE it.
But if we can't use a backplane, we should at least MITIGATE it: and we can do that by reducing the incoherency window.

And how?

Well, by simply specifying 2 different durations: one for the L1 and one for the L2.

Now, with FusionCache it has always been possible to specify a different Duration for the distributed cache, thanks to the DistributedCacheDuration option.

The problem was that, when in the scenario above (L1+L2 and no backplane), it would have been nice to be able to simply say "keep all the durations as alrady specified, and just refresh the data in the L1 from L2 every few seconds".

But with only the DistributedCacheDuration option available, the way to achieve this was counterintuitive: instead of somehow override the L1 duration, we needed to lower the normal Duration to a few seconds and specify the intended logical duration as the DistributedCacheDuration.

Not terrible, but not great.

But now, not anymore: enter MemoryCacheDuration.

We can of course go granular on a call-by-call basis, but there's something better: we can simply specify a value in the DefaultEntryOptions, and all the existing call sites will inherit this new value which will automatically override the duration only for the L1.

Done.

And, if we use Tagging we can simply do the same thing for the TagsDefaultEntryOptions, and we're done.

Something like this:

services.AddFusionCache()
	.WithOptions(options =>
	{
		options.DefaultEntryOptions.MemoryCacheDuration = TimeSpan.FromSeconds(5);
		options.TagsDefaultEntryOptions.MemoryCacheDuration = TimeSpan.FromSeconds(5);
	});

Oh, and the new Best Practices Advisor (see next point) can already give this advice when it detects such a scenario.

Nice 🙂

[!IMPORTANT]
It's important to say that, if you can, you should always use the backplane, as that is THE way to solve cache coherence for good without out-of-sync windows or other issues.

See here for the original issue.

🏅 Best Practices Advisor

Sometimes we may inadvertently fall into a scenario with:

  • a particularly strange combination of options
  • a particularly strange combination of components (distributed cache, backplane, etc)
  • a particularly strange combination of... both

With time FusionCache got more and more new components (like #​575 ) and options (like #​571 ) and this, along with the naturally dynamic nature of a flexible setup and configuration, may lead to inadvertently make the wrong decisions and fall into some gotchas.

FusionCache already had a couple of internal checks, like looking for a missing CacheKeyPrefix when using a shared L1 (which may lead to cache key collisions), and warns about them in the logs.

Now this practice has been unified & expanded, and it has a name: Best Practices Advisor.

Long story short, FusionCache now checks for common pitfalls and can give warnings and suggestions, all automatically and based on the current runtime state: no need to scrape the docs to see if the current config may lead to surprises thanks to a bad incantation of options.

I'd like to highlight that I've been careful about not trying to make it too smart for its own good: that is an easy to miss cliff that would lead to exaggerate in the implemented heuristics and checks, leading to bad results.

The checks initially implemented are:

  • missing cache key prefix: when using a named cache with an L2 or even just a shared L1, a missing cache key prefix may lead to cache key collisions
  • backplane + no distributed cache: when using a backplane without a distributed cache, it's important to check the default value for sending automatic distributed notifications, to avoid a useless continuous refresh cycle
  • distributed cache + no backplane + no memory cache duration: in this scenario it's probably better to use a lower memory cache duration to mitigate the cache coherence problem
  • distributed locker + no distributed cache: without a distributed cache it does not make much sense to use a distributed locker

More checks will be added in the future, but for now these are already quite useful.

Oh, one final thing: if you are thinking "great, a new piece of AI crap that will waste resources" then... nah, it's just a bunch of ifs done automatically in the background during startup. And if you want you can disable the Advisor by simply setting the new EnableBestPracticesAdvisor option to false (default is true).

See here for the original issue.

⚙️ New IgnoreTimeoutsWhenDebugging option

Community user @​tvardero asked if it was possible to automatically ignore all timeouts when debugging.

That was in fact an interesting feature request, and after some investigations I decided to proceed.

Now, when setting the new IgnoreTimeoutsWhenDebugging option to true, all timeouts will be ignored, but ONLY when there is a debugger attached (via Debugger.IsAttached).

All in all this will help when debugging issues locally, without nasty timeouts hitting simply because we are inspecting a variable after a breapoint hit, which is... the whole point of debugging, right?

Thanks @​tvardero for the input!

See here for the original issue.

See here for the feature design issue.

🕑 Small timestamps change

Thanks to community user @​vit-svoboda I changed the logic that gets the timestamp for a new entry, generated from a factory.
Before, the timestamp was about the moment the factory ended, now it's when it started.
No big change really, but it should help in a couple of edge cases with high concurrency.

See here for the original issue.

⚡ Minor performance tweaks

Nothing big really, as the perf were already great: just a bunch of extra tuning in a couple of edge cases, nothing big really.

📕 Docs (not yet!)

I did not have time to update the docs related to all this new stuff, but I'll do it in the next few days, pinky promise.

For now, this massive release note should be good enough.

✅ Tests

As always, with new features come new tests to make sure that all work as intended, now and in the future (regressions, am I right?).
Now we're up to 1534 total running tests, including params combinations & friends.

I can always do more, but still: not bad.

v2.4.0

🏷️ Add StaleTags to factory execution context

Community user @​ted-mundy noticed a tricky behavior when using Tagging with stale entries (see next point).

To solve it, I added a new StaleTags property to the factory execution context, so that now it's possible to access both the tags that are being passed to the GetOrSet() call and the existing tags of the stale entry in the cache (if any), like this:

cache.GetOrSet<string>(
  "foo",
  (ctx, token) => {
    // THE TAGS PASSED BELOW ("tag1", "tag2" and "tag3")
    ctx.Tags;
    // THE TAGS OF THE STALE ENTRY ALREADY IN THE CACHE, IF ANY
    ctx.StaleTags;
    
    return "Combo";
  },
  tags: ["tag1", "tag2", "tag3"]
);

This can be useful even in other scenarios, like applying some custom logic about what to do based on the tags already in the cache.

Nice.

🐞 Fix for tags with stale entries

As mentioned above, community user @​ted-mundy noticed a tricky behavior when using Tagging with stale entries.

Thanks to the addition of the new StaleTags property, this is now solved for good.

Thanks @​ted-mundy !

See here for the original issue.

Ⓜ️ Better entry options mapping with HybridCache adapter

Community user @​TheSpookyElectric noticed that, when working with the HybridCache adapter, the LocalCacheExpiration was not being handled correctly in all cases.

The mapping logic has been updated to account for that, and it now works as expected.

Thanks @​TheSpookyElectric !

See here for the original issue.

🐞 Fix for WithRegisteredSerializer()

Community user @​Inok noticed something... strange.
The builder, when calling WithRegisteredSerializer() on it, was doing something else: acting on the distributed cache.

That was, well... dumb, that's just what it was: my bad.

Now this has been solved.

Thanks @​Inok !

See here for the original issue.

🐞 Fix for InvalidOperationException when using AlwaysOffSampler with OTEL

Community user @​DFSko noticed an InvalidOperationException being thrown when working with OTEL and using the AlwaysOffSampler: this had to do with setting the current Activity after certain operations, in conjunction with activities with a state not compatible for being set as the current one.

This has now been solved.

Thanks @​DFSko !

See here for the original issue.

📕 Fix for an inline doc issue

Community user @​marcominerva discovered a wrong description for the WithDistributedCache() method on the builder: fixed.

Thanks @​marcominerva !

See here for the original issue.

✅ 1500 tests, huzza!

FusionCache reached 1500 tests:

image

That's it, that's the tweet 🙂

v2.3.0

🔑 Access the cache key in the factory context

Community user @​posledam and others asked for the ability to access the cache key in the factory context, to be able to work with it while going to the database or similar, without creating a closure with the lambda.

So that's what I added, but there's a catch here: FusionCache provides automatic cache key manipulation with things like CacheKeyPrefix usually in conjunction with things like Named Caches, so it would be nice to access both the original one and the processed one.

Therefore I added both of them in the factory context, and it can be used like this:

cache.GetOrSet<string>(
  "foo",
  (ctx, token) => {
    ctx.Key; // THE (PROCESSED) CACHE KEY
    ctx.OriginalKey; // THE (ORIGINAL) CACHE KEY
  }
);

See here for the original issue, and here for the design issue.

⚙️ New InternalStrings options

FusionCache automatically handles a lot of things for us, and to do that it may need to manipulate some strings used internally like the cache key or the backplane channel name.

For example to use the CacheName to automatically separate data in a shared cache when used in conjunction with other FusionCache instances, or the set of special cache keys used with Tagging or the way [wire format versioning]

[!IMPORTANT]
✂ PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • "every 2nd week starting on the 2 week of the year before 4am on Monday"
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about these updates again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@bitwarden-bot bitwarden-bot changed the title [deps]: Update fusioncache monorepo to 2.5.0 [PM-30419] [deps]: Update fusioncache monorepo to 2.5.0 Jan 5, 2026
@bitwarden-bot

Copy link
Copy Markdown
Collaborator

Internal tracking:

@renovate renovate Bot changed the title [PM-30419] [deps]: Update fusioncache monorepo to 2.5.0 [deps]: Update fusioncache monorepo to 2.5.0 Jan 5, 2026
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from 06fbefe to 474207c Compare February 2, 2026 15:42
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from 474207c to a5be458 Compare February 12, 2026 12:15
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch 2 times, most recently from a0a0e30 to 8253da3 Compare February 24, 2026 14:21
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from 8253da3 to 92488e2 Compare March 13, 2026 13:39
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from 92488e2 to b7ae354 Compare March 21, 2026 10:38
@renovate renovate Bot changed the title [deps]: Update fusioncache monorepo to 2.5.0 [deps]: Update fusioncache monorepo to 2.6.0 Mar 21, 2026
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch 2 times, most recently from dfb4c5b to 913a95e Compare April 8, 2026 15:04
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from 913a95e to 480bbd6 Compare April 21, 2026 17:50
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from 480bbd6 to 39e518f Compare May 7, 2026 17:09
@renovate renovate Bot changed the title [deps]: Update fusioncache monorepo to 2.6.0 [deps]: Update fusioncache monorepo to v2.6.0 May 7, 2026
@codecov

codecov Bot commented May 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.83%. Comparing base (df91576) to head (b95d487).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6796   +/-   ##
=======================================
  Coverage   74.83%   74.83%           
=======================================
  Files        2750     2750           
  Lines      117618   117618           
  Branches    10781    10781           
=======================================
+ Hits        88022    88023    +1     
+ Misses      26940    26939    -1     
  Partials     2656     2656           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@djsmith85
djsmith85 requested a review from a team May 12, 2026 08:47
@withinfocus
withinfocus requested a review from justindbaur May 13, 2026 17:09
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from 39e518f to c34efdc Compare May 18, 2026 11:43
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch 2 times, most recently from f4c8692 to c252c8b Compare July 19, 2026 04:00
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from c252c8b to 19ed322 Compare July 20, 2026 14:30
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch 2 times, most recently from 9a608a0 to 239768f Compare August 12, 2026 19:51
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from 239768f to 32ff265 Compare October 3, 2026 23:30
@renovate renovate Bot changed the title [deps]: Update fusioncache monorepo to v2.6.0 [deps]: Update fusioncache monorepo to v2.9.0 Oct 3, 2026
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch 4 times, most recently from 87d5fd4 to a126ea5 Compare October 27, 2026 08:29
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from a126ea5 to 46295ae Compare November 2, 2026 19:00
@renovate
renovate Bot force-pushed the renovate/fusioncache-monorepo branch from 46295ae to b95d487 Compare November 6, 2026 17:33

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant