Precache dynamic permission and refactor AbpPermissionManagementDomainModule class.

pull/13806/head
Halil İbrahim Kalkan 3 years ago
parent e260798b0d
commit 41ba0aba7c

@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Polly;
using Volo.Abp.Authorization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain;
@ -23,62 +24,124 @@ namespace Volo.Abp.PermissionManagement;
[DependsOn(typeof(AbpJsonModule))]
public class AbpPermissionManagementDomainModule : AbpModule
{
private readonly CancellationTokenSource _cancellationTokenSource = new();
public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
if (context
InitializeDynamicPermissions(context);
return Task.CompletedTask;
}
public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
{
_cancellationTokenSource.Cancel();
return Task.CompletedTask;
}
private void InitializeDynamicPermissions(ApplicationInitializationContext context)
{
var options = context
.ServiceProvider
.GetRequiredService<IOptions<PermissionManagementOptions>>()
.Value
.SaveStaticPermissionsToDatabase)
.Value;
if (!options.SaveStaticPermissionsToDatabase && !options.IsDynamicPermissionStoreEnabled)
{
SaveStaticPermissionsToDatabase(context);
return;
}
return Task.CompletedTask;
}
private static void SaveStaticPermissionsToDatabase(ApplicationInitializationContext context)
{
var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>();
var hostApplicationLifetime = context.ServiceProvider.GetService<IHostApplicationLifetime>();
var cancellationToken = hostApplicationLifetime?.ApplicationStopping ?? CancellationToken.None;
Task.Run(async () =>
{
using var scope = rootServiceProvider.CreateScope();
var applicationLifetime = scope.ServiceProvider.GetService<IHostApplicationLifetime>();
var cancellationTokenProvider = scope.ServiceProvider.GetRequiredService<ICancellationTokenProvider>();
var cancellationToken = applicationLifetime?.ApplicationStopping ?? _cancellationTokenSource.Token;
try
{
using (cancellationTokenProvider.Use(cancellationToken))
{
await Policy
.Handle<Exception>()
.WaitAndRetryAsync(8, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt) * 10))
.ExecuteAsync(async _ =>
{
try
{
// ReSharper disable once AccessToDisposedClosure
await scope
.ServiceProvider
.GetRequiredService<IStaticPermissionSaver>()
.SaveAsync();
}
catch (Exception ex)
{
// ReSharper disable once AccessToDisposedClosure
scope.ServiceProvider
.GetService<ILogger<AbpPermissionManagementDomainModule>>()?
.LogException(ex);
throw; // Polly will catch it
}
}, cancellationTokenProvider.Token);
if (cancellationTokenProvider.Token.IsCancellationRequested)
{
return;
}
await SaveStaticPermissionsToDatabaseAsync(options, scope, cancellationTokenProvider);
if (cancellationTokenProvider.Token.IsCancellationRequested)
{
return;
}
await PreCacheDynamicPermissionsAsync(options, scope);
}
}
// ReSharper disable once EmptyGeneralCatchClause (No need to log since it is logged above)
catch { }
});
}
private async static Task SaveStaticPermissionsToDatabaseAsync(
PermissionManagementOptions options,
IServiceScope scope,
ICancellationTokenProvider cancellationTokenProvider)
{
if (!options.SaveStaticPermissionsToDatabase)
{
return;
}
await Policy
.Handle<Exception>()
.WaitAndRetryAsync(8, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt) * 10))
.ExecuteAsync(async _ =>
{
try
{
// ReSharper disable once AccessToDisposedClosure
await scope
.ServiceProvider
.GetRequiredService<IStaticPermissionSaver>()
.SaveAsync();
}
catch (Exception ex)
{
// ReSharper disable once AccessToDisposedClosure
scope.ServiceProvider
.GetService<ILogger<AbpPermissionManagementDomainModule>>()?
.LogException(ex);
throw; // Polly will catch it
}
}, cancellationTokenProvider.Token);
}
private async static Task PreCacheDynamicPermissionsAsync(PermissionManagementOptions options, IServiceScope scope)
{
if (!options.IsDynamicPermissionStoreEnabled)
{
return;
}
try
{
// Pre-cache permissions, so first request doesn't wait
await scope
.ServiceProvider
.GetRequiredService<IDynamicPermissionDefinitionStore>()
.GetGroupsAsync();
}
catch (Exception ex)
{
// ReSharper disable once AccessToDisposedClosure
scope
.ServiceProvider
.GetService<ILogger<AbpPermissionManagementDomainModule>>()?
.LogException(ex);
throw; // It will be cached in InitializeDynamicPermissions
}
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
@ -49,9 +50,12 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor
{
return null;
}
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetPermissionOrNull(name);
using (await StoreCache.SyncSemaphore.LockAsync())
{
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetPermissionOrNull(name);
}
}
public virtual async Task<IReadOnlyList<PermissionDefinition>> GetPermissionsAsync()
@ -60,9 +64,12 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor
{
return Array.Empty<PermissionDefinition>();
}
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetPermissions();
using (await StoreCache.SyncSemaphore.LockAsync())
{
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetPermissions().ToImmutableList();
}
}
public virtual async Task<IReadOnlyList<PermissionGroupDefinition>> GetGroupsAsync()
@ -71,38 +78,38 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor
{
return Array.Empty<PermissionGroupDefinition>();
}
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetGroups();
}
private async Task EnsureCacheIsUptoDateAsync()
{
using (await StoreCache.SyncSemaphore.LockAsync())
{
if (StoreCache.LastCheckTime.HasValue &&
DateTime.Now.Subtract(StoreCache.LastCheckTime.Value).TotalSeconds < 30)
{
/* We get the latest permission with a small delay for optimization */
return;
}
var stampInDistributedCache = await GetOrSetStampInDistributedCache();
if (stampInDistributedCache == StoreCache.CacheStamp)
{
StoreCache.LastCheckTime = DateTime.Now;
return;
}
await UpdateInMemoryStoreCache();
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetGroups().ToImmutableList();
}
}
StoreCache.CacheStamp = stampInDistributedCache;
protected virtual async Task EnsureCacheIsUptoDateAsync()
{
if (StoreCache.LastCheckTime.HasValue &&
DateTime.Now.Subtract(StoreCache.LastCheckTime.Value).TotalSeconds < 30)
{
/* We get the latest permission with a small delay for optimization */
return;
}
var stampInDistributedCache = await GetOrSetStampInDistributedCache();
if (stampInDistributedCache == StoreCache.CacheStamp)
{
StoreCache.LastCheckTime = DateTime.Now;
return;
}
await UpdateInMemoryStoreCache();
StoreCache.CacheStamp = stampInDistributedCache;
StoreCache.LastCheckTime = DateTime.Now;
}
private async Task UpdateInMemoryStoreCache()
protected virtual async Task UpdateInMemoryStoreCache()
{
var permissionGroupRecords = await PermissionGroupRepository.GetListAsync();
var permissionRecords = await PermissionRepository.GetListAsync();
@ -110,7 +117,7 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor
await StoreCache.FillAsync(permissionGroupRecords, permissionRecords);
}
private async Task<string> GetOrSetStampInDistributedCache()
protected virtual async Task<string> GetOrSetStampInDistributedCache()
{
var cacheKey = GetCommonStampCacheKey();
@ -152,12 +159,12 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor
return stampInDistributedCache;
}
private string GetCommonStampCacheKey()
protected virtual string GetCommonStampCacheKey()
{
return $"{CacheOptions.KeyPrefix}_AbpInMemoryPermissionCacheStamp";
}
private string GetCommonDistributedLockKey()
protected virtual string GetCommonDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_Common_AbpPermissionUpdateLock";
}

Loading…
Cancel
Save