From a0406b1406377addf18b67adcf2d8df778bef9da Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 21 Jul 2025 13:19:42 +0200 Subject: [PATCH] Add defensive coding to the member application initializer (16) (#19764) * Add defensive coding to the member application initializer (#19760) * Moved _isInitialized to after the initialization --------- Co-authored-by: kjac --- ...izeMemberApplicationNotificationHandler.cs | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs index fdd98e1702..cf2ff26337 100644 --- a/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs +++ b/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs @@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Infrastructure.Security; namespace Umbraco.Cms.Api.Delivery.Handlers; @@ -18,18 +19,24 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica private readonly DeliveryApiSettings _deliveryApiSettings; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IMemberClientCredentialsManager _memberClientCredentialsManager; + private readonly IServerRoleAccessor _serverRoleAccessor; + + private static readonly SemaphoreSlim _locker = new(1); + private static bool _isInitialized = false; public InitializeMemberApplicationNotificationHandler( IRuntimeState runtimeState, IOptions deliveryApiSettings, ILogger logger, IServiceScopeFactory serviceScopeFactory, - IMemberClientCredentialsManager memberClientCredentialsManager) + IMemberClientCredentialsManager memberClientCredentialsManager, + IServerRoleAccessor serverRoleAccessor) { _runtimeState = runtimeState; _logger = logger; _serviceScopeFactory = serviceScopeFactory; _memberClientCredentialsManager = memberClientCredentialsManager; + _serverRoleAccessor = serverRoleAccessor; _deliveryApiSettings = deliveryApiSettings.Value; } @@ -40,13 +47,34 @@ internal sealed class InitializeMemberApplicationNotificationHandler : INotifica return; } - // we cannot inject the IMemberApplicationManager because it ultimately takes a dependency on the DbContext ... and during - // install that is not allowed (no connection string means no DbContext) - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IMemberApplicationManager memberApplicationManager = scope.ServiceProvider.GetRequiredService(); + if (_serverRoleAccessor.CurrentServerRole is ServerRole.Subscriber) + { + // subscriber instances should not alter the member application + return; + } - await HandleMemberApplication(memberApplicationManager, cancellationToken); - await HandleMemberClientCredentialsApplication(memberApplicationManager, cancellationToken); + try + { + await _locker.WaitAsync(cancellationToken); + if (_isInitialized) + { + return; + } + + // we cannot inject the IMemberApplicationManager because it ultimately takes a dependency on the DbContext ... and during + // install that is not allowed (no connection string means no DbContext) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMemberApplicationManager memberApplicationManager = scope.ServiceProvider.GetRequiredService(); + + await HandleMemberApplication(memberApplicationManager, cancellationToken); + await HandleMemberClientCredentialsApplication(memberApplicationManager, cancellationToken); + + _isInitialized = true; + } + finally + { + _locker.Release(); + } } private async Task HandleMemberApplication(IMemberApplicationManager memberApplicationManager, CancellationToken cancellationToken)