1
0
mirror of https://github.com/bitwarden/server synced 2026-01-07 11:03:37 +00:00

[PM-23845] Update cache service to handle concurrency (#6170)

This commit is contained in:
Jimmy Vo
2025-09-12 13:44:19 -04:00
committed by GitHub
parent 4e64d35f89
commit 854abb0993
15 changed files with 1392 additions and 10 deletions

View File

@@ -0,0 +1,152 @@
using Bit.Core.AdminConsole.AbilitiesCache;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Models.Data.Provider;
using Bit.Core.Models.Data.Organizations;
namespace Bit.Core.Services.Implementations;
/// <summary>
/// A feature-flagged routing service for application caching that bridges the gap between
/// scoped dependency injection (IFeatureService) and singleton services (cache implementations).
/// This service allows dynamic routing between IVCurrentInMemoryApplicationCacheService and
/// IVNextInMemoryApplicationCacheService based on the PM23845_VNextApplicationCache feature flag.
/// </summary>
/// <remarks>
/// This service is necessary because:
/// - IFeatureService is registered as Scoped in the DI container
/// - IVNextInMemoryApplicationCacheService and IVCurrentInMemoryApplicationCacheService are registered as Singleton
/// - We need to evaluate feature flags at request time while maintaining singleton cache behavior
///
/// The service acts as a scoped proxy that can access the scoped IFeatureService while
/// delegating actual cache operations to the appropriate singleton implementation.
/// </remarks>
public class FeatureRoutedCacheService(
IFeatureService featureService,
IVNextInMemoryApplicationCacheService vNextInMemoryApplicationCacheService,
IVCurrentInMemoryApplicationCacheService inMemoryApplicationCacheService,
IApplicationCacheServiceBusMessaging serviceBusMessaging)
: IApplicationCacheService
{
public async Task<IDictionary<Guid, OrganizationAbility>> GetOrganizationAbilitiesAsync()
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
return await vNextInMemoryApplicationCacheService.GetOrganizationAbilitiesAsync();
}
return await inMemoryApplicationCacheService.GetOrganizationAbilitiesAsync();
}
public async Task<OrganizationAbility?> GetOrganizationAbilityAsync(Guid orgId)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
return await vNextInMemoryApplicationCacheService.GetOrganizationAbilityAsync(orgId);
}
return await inMemoryApplicationCacheService.GetOrganizationAbilityAsync(orgId);
}
public async Task<IDictionary<Guid, ProviderAbility>> GetProviderAbilitiesAsync()
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
return await vNextInMemoryApplicationCacheService.GetProviderAbilitiesAsync();
}
return await inMemoryApplicationCacheService.GetProviderAbilitiesAsync();
}
public async Task UpsertOrganizationAbilityAsync(Organization organization)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.UpsertOrganizationAbilityAsync(organization);
await serviceBusMessaging.NotifyOrganizationAbilityUpsertedAsync(organization);
}
else
{
await inMemoryApplicationCacheService.UpsertOrganizationAbilityAsync(organization);
}
}
public async Task UpsertProviderAbilityAsync(Provider provider)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.UpsertProviderAbilityAsync(provider);
}
else
{
await inMemoryApplicationCacheService.UpsertProviderAbilityAsync(provider);
}
}
public async Task DeleteOrganizationAbilityAsync(Guid organizationId)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.DeleteOrganizationAbilityAsync(organizationId);
await serviceBusMessaging.NotifyOrganizationAbilityDeletedAsync(organizationId);
}
else
{
await inMemoryApplicationCacheService.DeleteOrganizationAbilityAsync(organizationId);
}
}
public async Task DeleteProviderAbilityAsync(Guid providerId)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.DeleteProviderAbilityAsync(providerId);
await serviceBusMessaging.NotifyProviderAbilityDeletedAsync(providerId);
}
else
{
await inMemoryApplicationCacheService.DeleteProviderAbilityAsync(providerId);
}
}
public async Task BaseUpsertOrganizationAbilityAsync(Organization organization)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.UpsertOrganizationAbilityAsync(organization);
}
else
{
// NOTE: This is a temporary workaround InMemoryServiceBusApplicationCacheService legacy implementation.
// Avoid using this approach in new code.
if (inMemoryApplicationCacheService is InMemoryServiceBusApplicationCacheService serviceBusCache)
{
await serviceBusCache.BaseUpsertOrganizationAbilityAsync(organization);
}
else
{
throw new InvalidOperationException($"Expected {nameof(inMemoryApplicationCacheService)} to be of type {nameof(InMemoryServiceBusApplicationCacheService)}");
}
}
}
public async Task BaseDeleteOrganizationAbilityAsync(Guid organizationId)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.DeleteOrganizationAbilityAsync(organizationId);
}
else
{
// NOTE: This is a temporary workaround InMemoryServiceBusApplicationCacheService legacy implementation.
// Avoid using this approach in new code.
if (inMemoryApplicationCacheService is InMemoryServiceBusApplicationCacheService serviceBusCache)
{
await serviceBusCache.BaseDeleteOrganizationAbilityAsync(organizationId);
}
else
{
throw new InvalidOperationException($"Expected {nameof(inMemoryApplicationCacheService)} to be of type {nameof(InMemoryServiceBusApplicationCacheService)}");
}
}
}
}

View File

@@ -1,6 +1,7 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using Bit.Core.AdminConsole.AbilitiesCache;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Models.Data.Provider;
@@ -10,7 +11,7 @@ using Bit.Core.Repositories;
namespace Bit.Core.Services;
public class InMemoryApplicationCacheService : IApplicationCacheService
public class InMemoryApplicationCacheService : IVCurrentInMemoryApplicationCacheService
{
private readonly IOrganizationRepository _organizationRepository;
private readonly IProviderRepository _providerRepository;

View File

@@ -8,9 +8,8 @@ using Bit.Core.Utilities;
namespace Bit.Core.Services;
public class InMemoryServiceBusApplicationCacheService : InMemoryApplicationCacheService, IApplicationCacheService
public class InMemoryServiceBusApplicationCacheService : InMemoryApplicationCacheService
{
private readonly ServiceBusClient _serviceBusClient;
private readonly ServiceBusSender _topicMessageSender;
private readonly string _subName;
@@ -21,7 +20,7 @@ public class InMemoryServiceBusApplicationCacheService : InMemoryApplicationCach
: base(organizationRepository, providerRepository)
{
_subName = CoreHelpers.GetApplicationCacheServiceBusSubscriptionName(globalSettings);
_serviceBusClient = new ServiceBusClient(globalSettings.ServiceBus.ConnectionString);
_topicMessageSender = new ServiceBusClient(globalSettings.ServiceBus.ConnectionString).CreateSender(globalSettings.ServiceBus.ApplicationCacheTopicName);
}