diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs index feb11fdee5..1e05590176 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs @@ -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>() - .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(); - var hostApplicationLifetime = context.ServiceProvider.GetService(); - var cancellationToken = hostApplicationLifetime?.ApplicationStopping ?? CancellationToken.None; Task.Run(async () => { using var scope = rootServiceProvider.CreateScope(); + var applicationLifetime = scope.ServiceProvider.GetService(); var cancellationTokenProvider = scope.ServiceProvider.GetRequiredService(); - + var cancellationToken = applicationLifetime?.ApplicationStopping ?? _cancellationTokenSource.Token; + try { using (cancellationTokenProvider.Use(cancellationToken)) { - await Policy - .Handle() - .WaitAndRetryAsync(8, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt) * 10)) - .ExecuteAsync(async _ => - { - try - { - // ReSharper disable once AccessToDisposedClosure - await scope - .ServiceProvider - .GetRequiredService() - .SaveAsync(); - } - catch (Exception ex) - { - // ReSharper disable once AccessToDisposedClosure - scope.ServiceProvider - .GetService>()? - .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() + .WaitAndRetryAsync(8, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt) * 10)) + .ExecuteAsync(async _ => + { + try + { + // ReSharper disable once AccessToDisposedClosure + await scope + .ServiceProvider + .GetRequiredService() + .SaveAsync(); + } + catch (Exception ex) + { + // ReSharper disable once AccessToDisposedClosure + scope.ServiceProvider + .GetService>()? + .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() + .GetGroupsAsync(); + } + catch (Exception ex) + { + // ReSharper disable once AccessToDisposedClosure + scope + .ServiceProvider + .GetService>()? + .LogException(ex); + + throw; // It will be cached in InitializeDynamicPermissions + } + } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs index 2a507c2ab5..f2e73cefc0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs @@ -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> GetPermissionsAsync() @@ -60,9 +64,12 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor { return Array.Empty(); } - - await EnsureCacheIsUptoDateAsync(); - return StoreCache.GetPermissions(); + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetPermissions().ToImmutableList(); + } } public virtual async Task> GetGroupsAsync() @@ -71,38 +78,38 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor { return Array.Empty(); } - - 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 GetOrSetStampInDistributedCache() + protected virtual async Task 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"; }