1
0
mirror of https://github.com/bitwarden/server synced 2025-12-28 06:03:29 +00:00

Add FusionCache to service collection (#6575)

* Add FusionCache to service collection

* Refactored to it's own service collection extension, added full unit tests, added TryAdd style support

* Move to ExtendedCache instead of FusionCache, re-use exsting DistributedCache if present, expose backplane to DI

* Reworked builders to reuse multiplexer if present
This commit is contained in:
Brant DeBow
2025-11-14 12:45:45 -05:00
committed by GitHub
parent 9b3adf0ddc
commit b4c7ab8773
4 changed files with 277 additions and 0 deletions

View File

@@ -68,6 +68,9 @@
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.14.0" />
<PackageReference Include="RabbitMQ.Client" Version="7.1.2" />
<PackageReference Include="ZiggyCreatures.FusionCache" Version="2.0.2" />
<PackageReference Include="ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis" Version="2.0.2" />
<PackageReference Include="ZiggyCreatures.FusionCache.Serialization.SystemTextJson" Version="2.0.2" />
</ItemGroup>
<ItemGroup Label="Pinned transitive dependencies">

View File

@@ -783,6 +783,19 @@ public class GlobalSettings : IGlobalSettings
{
public virtual IConnectionStringSettings Redis { get; set; } = new ConnectionStringSettings();
public virtual IConnectionStringSettings Cosmos { get; set; } = new ConnectionStringSettings();
public TimeSpan Duration { get; set; } = TimeSpan.FromMinutes(30);
public bool IsFailSafeEnabled { get; set; } = true;
public TimeSpan FailSafeMaxDuration { get; set; } = TimeSpan.FromHours(2);
public TimeSpan FailSafeThrottleDuration { get; set; } = TimeSpan.FromSeconds(30);
public float? EagerRefreshThreshold { get; set; } = 0.9f;
public TimeSpan FactorySoftTimeout { get; set; } = TimeSpan.FromMilliseconds(100);
public TimeSpan FactoryHardTimeout { get; set; } = TimeSpan.FromMilliseconds(1500);
public TimeSpan DistributedCacheSoftTimeout { get; set; } = TimeSpan.FromSeconds(1);
public TimeSpan DistributedCacheHardTimeout { get; set; } = TimeSpan.FromSeconds(2);
public bool AllowBackgroundDistributedCacheOperations { get; set; } = true;
public TimeSpan JitterMaxDuration { get; set; } = TimeSpan.FromSeconds(2);
public TimeSpan DistributedCacheCircuitBreakerDuration { get; set; } = TimeSpan.FromSeconds(30);
}
public class WebPushSettings : IWebPushSettings

View File

@@ -0,0 +1,90 @@
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StackExchange.Redis;
using ZiggyCreatures.Caching.Fusion;
using ZiggyCreatures.Caching.Fusion.Backplane;
using ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis;
using ZiggyCreatures.Caching.Fusion.Serialization.SystemTextJson;
namespace Microsoft.Extensions.DependencyInjection;
public static class ExtendedCacheServiceCollectionExtensions
{
/// <summary>
/// Add Fusion Cache <see href="https://github.com/ZiggyCreatures/FusionCache"/> to the service
/// collection.<br/>
/// <br/>
/// If Redis is configured, it uses Redis for an L2 cache and backplane. If not, it simply uses in-memory caching.
/// </summary>
public static IServiceCollection TryAddExtendedCacheServices(this IServiceCollection services, GlobalSettings globalSettings)
{
if (services.Any(s => s.ServiceType == typeof(IFusionCache)))
{
return services;
}
var fusionCacheBuilder = services.AddFusionCache()
.WithOptions(options =>
{
options.DistributedCacheCircuitBreakerDuration = globalSettings.DistributedCache.DistributedCacheCircuitBreakerDuration;
})
.WithDefaultEntryOptions(new FusionCacheEntryOptions
{
Duration = globalSettings.DistributedCache.Duration,
IsFailSafeEnabled = globalSettings.DistributedCache.IsFailSafeEnabled,
FailSafeMaxDuration = globalSettings.DistributedCache.FailSafeMaxDuration,
FailSafeThrottleDuration = globalSettings.DistributedCache.FailSafeThrottleDuration,
EagerRefreshThreshold = globalSettings.DistributedCache.EagerRefreshThreshold,
FactorySoftTimeout = globalSettings.DistributedCache.FactorySoftTimeout,
FactoryHardTimeout = globalSettings.DistributedCache.FactoryHardTimeout,
DistributedCacheSoftTimeout = globalSettings.DistributedCache.DistributedCacheSoftTimeout,
DistributedCacheHardTimeout = globalSettings.DistributedCache.DistributedCacheHardTimeout,
AllowBackgroundDistributedCacheOperations = globalSettings.DistributedCache.AllowBackgroundDistributedCacheOperations,
JitterMaxDuration = globalSettings.DistributedCache.JitterMaxDuration
})
.WithSerializer(
new FusionCacheSystemTextJsonSerializer()
);
if (!CoreHelpers.SettingHasValue(globalSettings.DistributedCache.Redis.ConnectionString))
{
return services;
}
services.TryAddSingleton<IConnectionMultiplexer>(sp =>
ConnectionMultiplexer.Connect(globalSettings.DistributedCache.Redis.ConnectionString));
fusionCacheBuilder
.WithDistributedCache(sp =>
{
var cache = sp.GetService<IDistributedCache>();
if (cache is not null)
{
return cache;
}
var mux = sp.GetRequiredService<IConnectionMultiplexer>();
return new RedisCache(new RedisCacheOptions
{
ConnectionMultiplexerFactory = () => Task.FromResult(mux)
});
})
.WithBackplane(sp =>
{
var backplane = sp.GetService<IFusionCacheBackplane>();
if (backplane is not null)
{
return backplane;
}
var mux = sp.GetRequiredService<IConnectionMultiplexer>();
return new RedisBackplane(new RedisBackplaneOptions
{
ConnectionMultiplexerFactory = () => Task.FromResult(mux)
});
});
return services;
}
}