From 935c3b8e4264a3b36ff6fc6d042acd7b0375e800 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 28 Oct 2024 12:10:38 +0100 Subject: [PATCH] Initialize important services before unattended installs (#17366) * Added new notification to hook in after the premigrations and use this to init different services. * Force MaxDegreeOfParallelism to 1, while investigating scopes * Tried some more workarounds * Updated scopes and changed parallel to non parallel to ensure migration works * Missing scope * Make it parallel again - The secret is, the SuppressFlow needs to be when you create the task, but not on the await!. * Fixed issue when DEBUG_SCOPES is not added to tests. * Remove test exception * Try build on ubuntu again, even that we know it can be stuck. Just a test to see if all tests pass * Updated comment --------- Co-authored-by: kjac --- build/azure-pipelines.yml | 2 +- .../Scoping/AmbientEFCoreScopeStack.cs | 31 +++++---- src/Umbraco.Core/Constants-Composing.cs | 2 +- .../DependencyInjection/UmbracoBuilder.cs | 3 +- ...RuntimePremigrationsUpgradeNotification.cs | 6 ++ .../Services/DocumentUrlServiceInitializer.cs | 53 --------------- ...rlServiceInitializerNotificationHandler.cs | 29 +++++++++ ...ationInitializationNotificationHandler.cs} | 18 ++---- ...tatusInitializationNotificationHandler.cs} | 18 ++---- .../Install/PremigrationUpgrader.cs | 2 +- .../Migrations/Upgrade/UmbracoPlan.cs | 4 +- .../Upgrade/UmbracoPremigrationPlan.cs | 2 + .../ConvertBlockEditorPropertiesBase.cs | 51 +++++++++++---- .../ConvertBlockGridEditorProperties.cs | 11 ++-- .../ConvertBlockListEditorProperties.cs | 11 ++-- .../ConvertRichTextEditorProperties.cs | 11 ++-- .../Runtime/CoreRuntime.cs | 4 ++ .../Scoping/AmbientScopeContextStack.cs | 29 ++++++--- .../Scoping/AmbientScopeStack.cs | 32 ++++++---- src/Umbraco.Infrastructure/Scoping/Scope.cs | 6 +- .../Scoping/ScopeProvider.cs | 14 ++-- .../SeedingNotificationHandler.cs | 6 +- .../Services/DocumentCacheService.cs | 64 +++++++++++-------- .../Services/MediaCacheService.cs | 33 ++++++---- .../UmbracoBuilderExtensions.cs | 4 +- .../Testing/UmbracoIntegrationTest.cs | 2 +- .../UmbracoIntegrationTestWithContent.cs | 2 +- .../Services/DocumentUrlServiceTest.cs | 19 ++---- ...cumentUrlServiceTest_hidetoplevel_false.cs | 8 ++- .../BackOfficeExamineSearcherTests.cs | 9 ++- .../UmbracoExamine/ExamineBaseTest.cs | 3 +- .../ExamineExternalIndexTests.cs | 3 + .../UmbracoExamine/IndexTest.cs | 10 +++ .../UmbracoExamine/SearchTests.cs | 9 +++ .../Migrations/MigrationTests.cs | 12 ++-- 35 files changed, 299 insertions(+), 224 deletions(-) create mode 100644 src/Umbraco.Core/Notifications/PostRuntimePremigrationsUpgradeNotification.cs delete mode 100644 src/Umbraco.Core/Services/DocumentUrlServiceInitializer.cs create mode 100644 src/Umbraco.Core/Services/DocumentUrlServiceInitializerNotificationHandler.cs rename src/Umbraco.Core/Services/Navigation/{NavigationInitializationHostedService.cs => NavigationInitializationNotificationHandler.cs} (66%) rename src/Umbraco.Core/Services/PublishStatus/{PublishStatusInitializationHostedService.cs => PublishStatusInitializationNotificationHandler.cs} (55%) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 602269f6c901..0e0dbcafc200 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -71,7 +71,7 @@ stages: - job: A displayName: Build Umbraco CMS pool: - vmImage: 'windows-latest' + vmImage: 'ubuntu-latest' steps: - checkout: self submodules: true diff --git a/src/Umbraco.Cms.Persistence.EFCore/Scoping/AmbientEFCoreScopeStack.cs b/src/Umbraco.Cms.Persistence.EFCore/Scoping/AmbientEFCoreScopeStack.cs index dc948f36f3a3..74fa71bdd6fb 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Scoping/AmbientEFCoreScopeStack.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Scoping/AmbientEFCoreScopeStack.cs @@ -5,36 +5,45 @@ namespace Umbraco.Cms.Persistence.EFCore.Scoping; public class AmbientEFCoreScopeStack : IAmbientEFCoreScopeStack where TDbContext : DbContext { - + private static Lock _lock = new(); private static AsyncLocal>> _stack = new(); public IEfCoreScope? AmbientScope { get { - if (_stack.Value?.TryPeek(out IEfCoreScope? ambientScope) ?? false) + lock (_lock) { - return ambientScope; - } + if (_stack.Value?.TryPeek(out IEfCoreScope? ambientScope) ?? false) + { + return ambientScope; + } - return null; + return null; + } } } public IEfCoreScope Pop() { - if (_stack.Value?.TryPop(out IEfCoreScope? ambientScope) ?? false) + lock (_lock) { - return ambientScope; - } + if (_stack.Value?.TryPop(out IEfCoreScope? ambientScope) ?? false) + { + return ambientScope; + } - throw new InvalidOperationException("No AmbientScope was found."); + throw new InvalidOperationException("No AmbientScope was found."); + } } public void Push(IEfCoreScope scope) { - _stack.Value ??= new ConcurrentStack>(); + lock (_lock) + { + _stack.Value ??= new ConcurrentStack>(); - _stack.Value.Push(scope); + _stack.Value.Push(scope); + } } } diff --git a/src/Umbraco.Core/Constants-Composing.cs b/src/Umbraco.Core/Constants-Composing.cs index defdf2fa932e..f5598c1795cf 100644 --- a/src/Umbraco.Core/Constants-Composing.cs +++ b/src/Umbraco.Core/Constants-Composing.cs @@ -12,7 +12,7 @@ public static class Composing { public static readonly string[] UmbracoCoreAssemblyNames = { - "Umbraco.Core", "Umbraco.Infrastructure", "Umbraco.PublishedCache.NuCache", "Umbraco.Examine.Lucene", + "Umbraco.Core", "Umbraco.Infrastructure", "Umbraco.Examine.Lucene", "Umbraco.Web.Common", "Umbraco.Cms.Api.Common","Umbraco.Cms.Api.Delivery","Umbraco.Cms.Api.Management", "Umbraco.Web.Website", }; } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index d702ce479e09..9fb195481f77 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -416,7 +416,8 @@ private void AddCoreServices() // Routing Services.AddUnique(); - Services.AddHostedService(); + Services.AddNotificationAsyncHandler(); + } } } diff --git a/src/Umbraco.Core/Notifications/PostRuntimePremigrationsUpgradeNotification.cs b/src/Umbraco.Core/Notifications/PostRuntimePremigrationsUpgradeNotification.cs new file mode 100644 index 000000000000..2cbea10692ca --- /dev/null +++ b/src/Umbraco.Core/Notifications/PostRuntimePremigrationsUpgradeNotification.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Core.Notifications; + +public class PostRuntimePremigrationsUpgradeNotification : INotification +{ + +} diff --git a/src/Umbraco.Core/Services/DocumentUrlServiceInitializer.cs b/src/Umbraco.Core/Services/DocumentUrlServiceInitializer.cs deleted file mode 100644 index 4c99b9a8ae1c..000000000000 --- a/src/Umbraco.Core/Services/DocumentUrlServiceInitializer.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Microsoft.Extensions.Hosting; - -namespace Umbraco.Cms.Core.Services; - -public class DocumentUrlServiceInitializer : IHostedLifecycleService -{ - private readonly IDocumentUrlService _documentUrlService; - private readonly IRuntimeState _runtimeState; - - public DocumentUrlServiceInitializer(IDocumentUrlService documentUrlService, IRuntimeState runtimeState) - { - _documentUrlService = documentUrlService; - _runtimeState = runtimeState; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - if (_runtimeState.Level == RuntimeLevel.Upgrade) - { - //Special case on the first upgrade, as the database is not ready yet. - return; - } - - await _documentUrlService.InitAsync( - _runtimeState.Level <= RuntimeLevel.Install, - cancellationToken); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - public Task StartingAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - public Task StartedAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - public Task StoppingAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - public Task StoppedAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } -} diff --git a/src/Umbraco.Core/Services/DocumentUrlServiceInitializerNotificationHandler.cs b/src/Umbraco.Core/Services/DocumentUrlServiceInitializerNotificationHandler.cs new file mode 100644 index 000000000000..a10843f6e6cd --- /dev/null +++ b/src/Umbraco.Core/Services/DocumentUrlServiceInitializerNotificationHandler.cs @@ -0,0 +1,29 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco.Cms.Core.Services; + +public class DocumentUrlServiceInitializerNotificationHandler : INotificationAsyncHandler +{ + private readonly IDocumentUrlService _documentUrlService; + private readonly IRuntimeState _runtimeState; + + public DocumentUrlServiceInitializerNotificationHandler(IDocumentUrlService documentUrlService, IRuntimeState runtimeState) + { + _documentUrlService = documentUrlService; + _runtimeState = runtimeState; + } + + public async Task HandleAsync(UmbracoApplicationStartingNotification notification, CancellationToken cancellationToken) + { + if (_runtimeState.Level == RuntimeLevel.Upgrade) + { + //Special case on the first upgrade, as the database is not ready yet. + return; + } + + await _documentUrlService.InitAsync( + _runtimeState.Level <= RuntimeLevel.Install, + cancellationToken); + } +} diff --git a/src/Umbraco.Core/Services/Navigation/NavigationInitializationHostedService.cs b/src/Umbraco.Core/Services/Navigation/NavigationInitializationNotificationHandler.cs similarity index 66% rename from src/Umbraco.Core/Services/Navigation/NavigationInitializationHostedService.cs rename to src/Umbraco.Core/Services/Navigation/NavigationInitializationNotificationHandler.cs index a11b55b7b840..693c3bf6aa52 100644 --- a/src/Umbraco.Core/Services/Navigation/NavigationInitializationHostedService.cs +++ b/src/Umbraco.Core/Services/Navigation/NavigationInitializationNotificationHandler.cs @@ -1,4 +1,6 @@ using Microsoft.Extensions.Hosting; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; namespace Umbraco.Cms.Core.Services.Navigation; @@ -6,13 +8,13 @@ namespace Umbraco.Cms.Core.Services.Navigation; /// Responsible for seeding the in-memory navigation structures at application's startup /// by rebuild the navigation structures. /// -public sealed class NavigationInitializationHostedService : IHostedLifecycleService +public sealed class NavigationInitializationNotificationHandler : INotificationAsyncHandler { private readonly IRuntimeState _runtimeState; private readonly IDocumentNavigationManagementService _documentNavigationManagementService; private readonly IMediaNavigationManagementService _mediaNavigationManagementService; - public NavigationInitializationHostedService( + public NavigationInitializationNotificationHandler( IRuntimeState runtimeState, IDocumentNavigationManagementService documentNavigationManagementService, IMediaNavigationManagementService mediaNavigationManagementService) @@ -22,7 +24,7 @@ public NavigationInitializationHostedService( _mediaNavigationManagementService = mediaNavigationManagementService; } - public async Task StartingAsync(CancellationToken cancellationToken) + public async Task HandleAsync(PostRuntimePremigrationsUpgradeNotification notification, CancellationToken cancellationToken) { if(_runtimeState.Level < RuntimeLevel.Upgrade) { @@ -34,14 +36,4 @@ public async Task StartingAsync(CancellationToken cancellationToken) await _mediaNavigationManagementService.RebuildAsync(); await _mediaNavigationManagementService.RebuildBinAsync(); } - - public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public Task StoppingAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask; } diff --git a/src/Umbraco.Core/Services/PublishStatus/PublishStatusInitializationHostedService.cs b/src/Umbraco.Core/Services/PublishStatus/PublishStatusInitializationNotificationHandler.cs similarity index 55% rename from src/Umbraco.Core/Services/PublishStatus/PublishStatusInitializationHostedService.cs rename to src/Umbraco.Core/Services/PublishStatus/PublishStatusInitializationNotificationHandler.cs index b0d3583a6044..f795a49d3240 100644 --- a/src/Umbraco.Core/Services/PublishStatus/PublishStatusInitializationHostedService.cs +++ b/src/Umbraco.Core/Services/PublishStatus/PublishStatusInitializationNotificationHandler.cs @@ -1,4 +1,6 @@ using Microsoft.Extensions.Hosting; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; namespace Umbraco.Cms.Core.Services.Navigation; @@ -6,12 +8,12 @@ namespace Umbraco.Cms.Core.Services.Navigation; /// Responsible for seeding the in-memory publish status cache at application's startup /// by loading all data from the database. /// -public sealed class PublishStatusInitializationHostedService : IHostedLifecycleService +public sealed class PublishStatusInitializationNotificationHandler : INotificationAsyncHandler { private readonly IRuntimeState _runtimeState; private readonly IPublishStatusManagementService _publishStatusManagementService; - public PublishStatusInitializationHostedService( + public PublishStatusInitializationNotificationHandler( IRuntimeState runtimeState, IPublishStatusManagementService publishStatusManagementService ) @@ -20,7 +22,7 @@ IPublishStatusManagementService publishStatusManagementService _publishStatusManagementService = publishStatusManagementService; } - public async Task StartingAsync(CancellationToken cancellationToken) + public async Task HandleAsync(PostRuntimePremigrationsUpgradeNotification notification, CancellationToken cancellationToken) { if(_runtimeState.Level < RuntimeLevel.Upgrade) { @@ -29,14 +31,4 @@ public async Task StartingAsync(CancellationToken cancellationToken) await _publishStatusManagementService.InitializeAsync(cancellationToken); } - - public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public Task StoppingAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask; } diff --git a/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs b/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs index a3307a347b2d..1be5cb1ee8e7 100644 --- a/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs +++ b/src/Umbraco.Infrastructure/Install/PremigrationUpgrader.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Infrastructure.Install; /// -/// Handles to execute the unattended Umbraco upgrader +/// Handles to execute the unattended Umbraco upgrader /// or the unattended Package migrations runner. /// public class PremigrationUpgrader : INotificationAsyncHandler diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 5b09ef3fc2e0..6c4becfc6d25 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -66,14 +66,14 @@ protected virtual void DefinePlan() To("{CC47C751-A81B-489A-A2BC-0240245DB687}"); // To 14.0.0 - To("{419827A0-4FCE-464B-A8F3-247C6092AF55}"); + To("{419827A0-4FCE-464B-A8F3-247C6092AF55}"); To("{69E12556-D9B3-493A-8E8A-65EC89FB658D}"); To("{F2B16CD4-F181-4BEE-81C9-11CF384E6025}"); To("{A8E01644-9F2E-4988-8341-587EF5B7EA69}"); To("{E073DBC0-9E8E-4C92-8210-9CB18364F46E}"); To("{80D282A4-5497-47FF-991F-BC0BCE603121}"); To("{96525697-E9DC-4198-B136-25AD033442B8}"); - To("{7FC5AC9B-6F56-415B-913E-4A900629B853}"); + To("{7FC5AC9B-6F56-415B-913E-4A900629B853}"); To("{1539A010-2EB5-4163-8518-4AE2AA98AFC6}"); To("{C567DE81-DF92-4B99-BEA8-CD34EF99DA5D}"); To("{0D82C836-96DD-480D-A924-7964E458BD34}"); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs index 4c52f8867cbc..3b8fd4f622df 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs @@ -65,5 +65,7 @@ protected virtual void DefinePlan() To("{A08254B6-D9E7-4207-A496-2ED0A87FB4FD}"); To("{69AA6889-8B67-42B4-AA4F-114704487A45}"); To("{B9133686-B758-404D-AF12-708AA80C7E44}"); + To("{EEB1F012-B44D-4AB4-8756-F7FB547345B4}"); + To("{0F49E1A4-AFD8-4673-A91B-F64E78C48174}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockEditorPropertiesBase.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockEditorPropertiesBase.cs index f60c7f049f77..569f21afd2f9 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockEditorPropertiesBase.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockEditorPropertiesBase.cs @@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -23,6 +24,7 @@ public abstract class ConvertBlockEditorPropertiesBase : MigrationBase private readonly IJsonSerializer _jsonSerializer; private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly ILanguageService _languageService; + private readonly ICoreScopeProvider _coreScopeProvider; protected abstract IEnumerable PropertyEditorAliases { get; } @@ -44,7 +46,8 @@ public ConvertBlockEditorPropertiesBase( IDataTypeService dataTypeService, IJsonSerializer jsonSerializer, IUmbracoContextFactory umbracoContextFactory, - ILanguageService languageService) + ILanguageService languageService, + ICoreScopeProvider coreScopeProvider) : base(context) { _logger = logger; @@ -53,6 +56,7 @@ public ConvertBlockEditorPropertiesBase( _jsonSerializer = jsonSerializer; _umbracoContextFactory = umbracoContextFactory; _languageService = languageService; + _coreScopeProvider = coreScopeProvider; } protected override void Migrate() @@ -71,6 +75,7 @@ protected override void Migrate() .GroupBy(pt => pt.PropertyEditorAlias) .ToDictionary(group => group.Key, group => group.ToArray()); + foreach (var propertyEditorAlias in PropertyEditorAliases) { if (allPropertyTypesByEditor.TryGetValue(propertyEditorAlias, out IPropertyType[]? propertyTypes) is false) @@ -98,11 +103,17 @@ private bool Handle(IPropertyType[] propertyTypes, IDictionary l { var success = true; - foreach (IPropertyType propertyType in propertyTypes) + var propertyTypeCount = propertyTypes.Length; + for (var propertyTypeIndex = 0; propertyTypeIndex < propertyTypeCount; propertyTypeIndex++) { + IPropertyType propertyType = propertyTypes[propertyTypeIndex]; try { - _logger.LogInformation("- starting property type: {propertyTypeName} (id: {propertyTypeId}, alias: {propertyTypeAlias})...", propertyType.Name, propertyType.Id, propertyType.Alias); + _logger.LogInformation( + "- starting property type {propertyTypeIndex}/{propertyTypeCount} : {propertyTypeName} (id: {propertyTypeId}, alias: {propertyTypeAlias})...", + propertyTypeIndex+1, + propertyTypeCount, + propertyType.Name, propertyType.Id, propertyType.Alias); IDataType dataType = _dataTypeService.GetAsync(propertyType.DataTypeKey).GetAwaiter().GetResult() ?? throw new InvalidOperationException("The data type could not be fetched."); @@ -113,7 +124,8 @@ private bool Handle(IPropertyType[] propertyTypes, IDictionary l } IDataValueEditor valueEditor = dataType.Editor?.GetValueEditor() - ?? throw new InvalidOperationException("The data type value editor could not be fetched."); + ?? throw new InvalidOperationException( + "The data type value editor could not be fetched."); Sql sql = Sql() .Select() @@ -132,15 +144,24 @@ private bool Handle(IPropertyType[] propertyTypes, IDictionary l var progress = 0; - ExecutionContext.SuppressFlow(); - Parallel.ForEach(updateBatch, update => + Parallel.ForEachAsync(updateBatch, async (update, token) => { - using UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); + //Foreach here, but we need to suppress the flow before each task, but not the actuall await of the task + Task task; + using (ExecutionContext.SuppressFlow()) + { + task = Task.Run(() => + { + using ICoreScope scope = _coreScopeProvider.CreateCoreScope(); + scope.Complete(); + using UmbracoContextReference umbracoContextReference = + _umbracoContextFactory.EnsureUmbracoContext(); progress++; if (progress % 100 == 0) { - _logger.LogInformation(" - finíshed {progress} of {total} properties", progress, updateBatch.Count); + _logger.LogInformation(" - finíshed {progress} of {total} properties", progress, + updateBatch.Count); } PropertyDataDto propertyDataDto = update.Poco; @@ -148,7 +169,8 @@ private bool Handle(IPropertyType[] propertyTypes, IDictionary l // NOTE: some old property data DTOs can have variance defined, even if the property type no longer varies var culture = propertyType.VariesByCulture() && propertyDataDto.LanguageId.HasValue - && languagesById.TryGetValue(propertyDataDto.LanguageId.Value, out ILanguage? language) + && languagesById.TryGetValue(propertyDataDto.LanguageId.Value, + out ILanguage? language) ? language.IsoCode : null; @@ -211,6 +233,7 @@ private bool Handle(IPropertyType[] propertyTypes, IDictionary l default: throw new ArgumentOutOfRangeException(); } + break; } @@ -233,8 +256,11 @@ private bool Handle(IPropertyType[] propertyTypes, IDictionary l stringValue = UpdateDatabaseValue(stringValue); propertyDataDto.TextValue = stringValue; - }); - ExecutionContext.RestoreFlow(); + }, token); + } + + await task; + }).GetAwaiter().GetResult(); updateBatch.RemoveAll(updatesToSkip.Contains); @@ -248,7 +274,8 @@ private bool Handle(IPropertyType[] propertyTypes, IDictionary l var result = Database.UpdateBatch(updateBatch, new BatchOptions { BatchSize = 100 }); if (result != updateBatch.Count) { - throw new InvalidOperationException($"The database batch update was supposed to update {updateBatch.Count} property DTO entries, but it updated {result} entries."); + throw new InvalidOperationException( + $"The database batch update was supposed to update {updateBatch.Count} property DTO entries, but it updated {result} entries."); } _logger.LogDebug( diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockGridEditorProperties.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockGridEditorProperties.cs index cc80a77ea9c0..64fc64a961b6 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockGridEditorProperties.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockGridEditorProperties.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -19,8 +20,9 @@ public ConvertBlockGridEditorProperties( IJsonSerializer jsonSerializer, IUmbracoContextFactory umbracoContextFactory, ILanguageService languageService, - IOptions options) - : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService) + IOptions options, + ICoreScopeProvider coreScopeProvider) + : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService, coreScopeProvider) => SkipMigration = options.Value.SkipBlockGridEditors; protected override IEnumerable PropertyEditorAliases @@ -40,8 +42,9 @@ public ConvertBlockGridEditorProperties( IDataTypeService dataTypeService, IJsonSerializer jsonSerializer, IUmbracoContextFactory umbracoContextFactory, - ILanguageService languageService) - : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService) + ILanguageService languageService, + ICoreScopeProvider coreScopeProvider) + : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService, coreScopeProvider) { } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockListEditorProperties.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockListEditorProperties.cs index e920a3b6d852..5ea86f07f3d0 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockListEditorProperties.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockListEditorProperties.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -19,8 +20,9 @@ public ConvertBlockListEditorProperties( IJsonSerializer jsonSerializer, IUmbracoContextFactory umbracoContextFactory, ILanguageService languageService, - IOptions options) - : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService) + IOptions options, + ICoreScopeProvider coreScopeProvider) + : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService, coreScopeProvider) => SkipMigration = options.Value.SkipBlockListEditors; protected override IEnumerable PropertyEditorAliases @@ -40,8 +42,9 @@ public ConvertBlockListEditorProperties( IDataTypeService dataTypeService, IJsonSerializer jsonSerializer, IUmbracoContextFactory umbracoContextFactory, - ILanguageService languageService) - : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService) + ILanguageService languageService, + ICoreScopeProvider coreScopeProvider) + : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService, coreScopeProvider) { } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertRichTextEditorProperties.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertRichTextEditorProperties.cs index 6023917b4cd1..60c143805956 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertRichTextEditorProperties.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertRichTextEditorProperties.cs @@ -4,6 +4,7 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -21,8 +22,9 @@ public ConvertRichTextEditorProperties( IJsonSerializer jsonSerializer, IUmbracoContextFactory umbracoContextFactory, ILanguageService languageService, - IOptions options) - : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService) + IOptions options, + ICoreScopeProvider coreScopeProvider) + : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService, coreScopeProvider) => SkipMigration = options.Value.SkipRichTextEditors; protected override IEnumerable PropertyEditorAliases @@ -60,8 +62,9 @@ public ConvertRichTextEditorProperties( IDataTypeService dataTypeService, IJsonSerializer jsonSerializer, IUmbracoContextFactory umbracoContextFactory, - ILanguageService languageService) - : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService) + ILanguageService languageService, + ICoreScopeProvider coreScopeProvider) + : base(context, logger, contentTypeService, dataTypeService, jsonSerializer, umbracoContextFactory, languageService, coreScopeProvider) { } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 2e4604904238..d28829dd4d0f 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -199,6 +199,10 @@ private async Task StartAsync(CancellationToken cancellationToken, bool isRestar break; } + // + var postRuntimePremigrationsUpgradeNotification = new PostRuntimePremigrationsUpgradeNotification(); + await _eventAggregator.PublishAsync(postRuntimePremigrationsUpgradeNotification, cancellationToken); + // If level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade var unattendedUpgradeNotification = new RuntimeUnattendedUpgradeNotification(); await _eventAggregator.PublishAsync(unattendedUpgradeNotification, cancellationToken); diff --git a/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs b/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs index 18e68120a625..1d2a947ff751 100644 --- a/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs +++ b/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs @@ -5,35 +5,46 @@ namespace Umbraco.Cms.Infrastructure.Scoping; internal class AmbientScopeContextStack : IAmbientScopeContextStack { + private static Lock _lock = new(); private static AsyncLocal> _stack = new(); public IScopeContext? AmbientContext { get { - if (_stack.Value?.TryPeek(out IScopeContext? ambientContext) ?? false) + lock (_lock) { - return ambientContext; + if (_stack.Value?.TryPeek(out IScopeContext? ambientContext) ?? false) + { + return ambientContext; + } + + return null; } - return null; } } public IScopeContext Pop() { - if (_stack.Value?.TryPop(out IScopeContext? ambientContext) ?? false) + lock (_lock) { - return ambientContext; - } + if (_stack.Value?.TryPop(out IScopeContext? ambientContext) ?? false) + { + return ambientContext; + } - throw new InvalidOperationException("No AmbientContext was found."); + throw new InvalidOperationException("No AmbientContext was found."); + } } public void Push(IScopeContext scope) { - _stack.Value ??= new ConcurrentStack(); + lock (_lock) + { + _stack.Value ??= new ConcurrentStack(); - _stack.Value.Push(scope); + _stack.Value.Push(scope); + } } } diff --git a/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs b/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs index 3ad5e89e5113..efb5ea27acae 100644 --- a/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs +++ b/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs @@ -4,36 +4,46 @@ namespace Umbraco.Cms.Infrastructure.Scoping { internal class AmbientScopeStack : IAmbientScopeStack { + private static Lock _lock = new(); private static AsyncLocal> _stack = new (); public IScope? AmbientScope { get { - if (_stack.Value?.TryPeek(out IScope? ambientScope) ?? false) + lock (_lock) { - return ambientScope; - } + if (_stack.Value?.TryPeek(out IScope? ambientScope) ?? false) + { + return ambientScope; + } - return null; + return null; + } } } public IScope Pop() { - if (_stack.Value?.TryPop(out IScope? ambientScope) ?? false) + lock (_lock) { - return ambientScope; - } - throw new InvalidOperationException("No AmbientScope was found."); + + if (_stack.Value?.TryPop(out IScope? ambientScope) ?? false) + { + return ambientScope; + } + + throw new InvalidOperationException("No AmbientScope was found."); + } } public void Push(IScope scope) { - _stack.Value ??= new ConcurrentStack(); - - _stack.Value.Push(scope); + lock (_lock) + { + (_stack.Value ??= new ConcurrentStack()).Push(scope); + } } } } diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 3c1495f9c1ef..ca7cd28ef246 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -367,7 +367,7 @@ public override void Dispose() $"The {nameof(Scope)} {InstanceId} being disposed is not the Ambient {nameof(Scope)} {_scopeProvider.AmbientScope?.InstanceId.ToString() ?? "NULL"}. This typically indicates that a child {nameof(Scope)} was not disposed, or flowed to a child thread that was not awaited, or concurrent threads are accessing the same {nameof(Scope)} (Ambient context) which is not supported. If using Task.Run (or similar) as a fire and forget tasks or to run threads in parallel you must suppress execution context flow with ExecutionContext.SuppressFlow() and ExecutionContext.RestoreFlow()."; #if DEBUG_SCOPES - Scope ambient = _scopeProvider.AmbientScope; + Scope? ambient = _scopeProvider.AmbientScope; _logger.LogWarning("Dispose error (" + (ambient == null ? "no" : "other") + " ambient)"); if (ambient == null) { @@ -377,8 +377,8 @@ public override void Dispose() ScopeInfo ambientInfos = _scopeProvider.GetScopeInfo(ambient); ScopeInfo disposeInfos = _scopeProvider.GetScopeInfo(this); throw new InvalidOperationException($"{failedMessage} (see ctor stack traces).\r\n" - + "- ambient ->\r\n" + ambientInfos.ToString() + "\r\n" - + "- dispose ->\r\n" + disposeInfos.ToString() + "\r\n"); + + "- ambient ->\r\n" + ambientInfos + "\r\n" + + "- dispose ->\r\n" + disposeInfos + "\r\n"); #else throw new InvalidOperationException(failedMessage); #endif diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index 97d01a56a11d..a88cd01338c5 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -267,7 +267,7 @@ public ScopeInfo GetScopeInfo(IScope scope) { lock (s_staticScopeInfosLock) { - return s_staticScopeInfos.TryGetValue(scope, out ScopeInfo scopeInfo) ? scopeInfo : null; + return s_staticScopeInfos.TryGetValue(scope, out ScopeInfo? scopeInfo) ? scopeInfo : null!; } } @@ -302,7 +302,7 @@ public void RegisterContext(IScope scope, string context) lock (s_staticScopeInfosLock) { - if (s_staticScopeInfos.TryGetValue(scope, out ScopeInfo info) == false) + if (s_staticScopeInfos.TryGetValue(scope, out ScopeInfo? info) == false) { info = null; } @@ -327,7 +327,7 @@ public void RegisterContext(IScope scope, string context) sb.Append(s.InstanceId.ToString("N").Substring(0, 8)); var ss = s as Scope; - s = ss?.ParentScope; + s = ss?.ParentScope!; } _logger.LogTrace("Register " + (context ?? "null") + " context " + sb); @@ -339,7 +339,7 @@ public void RegisterContext(IScope scope, string context) _logger.LogTrace("At:\r\n" + Head(Environment.StackTrace, 16)); - info.Context = context; + info.Context = context!; } } @@ -431,15 +431,15 @@ public ScopeInfo(IScope scope, string ctorStack) public IScope Scope { get; } // the scope itself // the scope's parent identifier - public Guid Parent => ((Scope)Scope).ParentScope == null ? Guid.Empty : ((Scope)Scope).ParentScope.InstanceId; + public Guid Parent => ((Scope)Scope).ParentScope == null ? Guid.Empty : ((Scope)Scope).ParentScope!.InstanceId; public DateTime Created { get; } // the date time the scope was created public bool Disposed { get; set; } // whether the scope has been disposed already - public string Context { get; set; } // the current 'context' that contains the scope (null, "http" or "lcc") + public string Context { get; set; }= string.Empty; // the current 'context' that contains the scope (null, "http" or "lcc") public string CtorStack { get; } // the stacktrace of the scope ctor //public string DisposedStack { get; set; } // the stacktrace when disposed - public string NullStack { get; set; } // the stacktrace when the 'context' that contains the scope went null + public string NullStack { get; set; } = string.Empty; // the stacktrace when the 'context' that contains the scope went null public override string ToString() => new StringBuilder() .AppendLine("ScopeInfo:") diff --git a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/SeedingNotificationHandler.cs b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/SeedingNotificationHandler.cs index 72d966830650..196dd3950e05 100644 --- a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/SeedingNotificationHandler.cs +++ b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/SeedingNotificationHandler.cs @@ -32,9 +32,7 @@ public async Task HandleAsync(UmbracoApplicationStartedNotification notification return; } - await Task.WhenAll( - _documentCacheService.SeedAsync(cancellationToken), - _mediaCacheService.SeedAsync(cancellationToken) - ); + await _documentCacheService.SeedAsync(cancellationToken); + await _mediaCacheService.SeedAsync(cancellationToken); } } diff --git a/src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs b/src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs index b73f48dcd7ad..011f0cd24b17 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs @@ -72,15 +72,18 @@ public DocumentCacheService( public async Task GetByKeyAsync(Guid key, bool? preview = null) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(); - bool calculatedPreview = preview ?? GetPreview(); ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync( GetCacheKey(key, calculatedPreview), // Unique key to the cache entry - async cancel => await _databaseCacheRepository.GetContentSourceAsync(key, calculatedPreview)); + async cancel => + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + ContentCacheNode? contentCacheNode = await _databaseCacheRepository.GetContentSourceAsync(key, calculatedPreview); + scope.Complete(); + return contentCacheNode; + }); - scope.Complete(); return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedContent(contentCacheNode, calculatedPreview).CreateModel(_publishedModelFactory); } @@ -99,11 +102,16 @@ private bool GetPreview() bool calculatedPreview = preview ?? GetPreview(); - using ICoreScope scope = _scopeProvider.CreateCoreScope(); ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync( GetCacheKey(keyAttempt.Result, calculatedPreview), // Unique key to the cache entry - async cancel => await _databaseCacheRepository.GetContentSourceAsync(id, calculatedPreview)); - scope.Complete(); + async cancel => + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + ContentCacheNode? contentCacheNode = await _databaseCacheRepository.GetContentSourceAsync(id, calculatedPreview); + scope.Complete(); + return contentCacheNode; + }); + return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedContent(contentCacheNode, calculatedPreview).CreateModel(_publishedModelFactory);; } @@ -120,8 +128,6 @@ public IEnumerable GetByContentType(IPublishedContentType con public async Task SeedAsync(CancellationToken cancellationToken) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(); - foreach (Guid key in SeedKeys) { if(cancellationToken.IsCancellationRequested) @@ -131,31 +137,32 @@ public async Task SeedAsync(CancellationToken cancellationToken) var cacheKey = GetCacheKey(key, false); - // We'll use GetOrCreateAsync because it may be in the second level cache, in which case we don't have to re-seed. - ContentCacheNode? cachedValue = await _hybridCache.GetOrCreateAsync( - cacheKey, - async cancel => - { - ContentCacheNode? cacheNode = await _databaseCacheRepository.GetContentSourceAsync(key, false); - - // We don't want to seed drafts - if (cacheNode is null || cacheNode.IsDraft) + // We'll use GetOrCreateAsync because it may be in the second level cache, in which case we don't have to re-seed. + ContentCacheNode? cachedValue = await _hybridCache.GetOrCreateAsync( + cacheKey, + async cancel => { - return null; - } + using ICoreScope scope = _scopeProvider.CreateCoreScope(); - return cacheNode; - }, - GetSeedEntryOptions()); + ContentCacheNode? cacheNode = await _databaseCacheRepository.GetContentSourceAsync(key, false); + + scope.Complete(); + // We don't want to seed drafts + if (cacheNode is null || cacheNode.IsDraft) + { + return null; + } + + return cacheNode; + }, + GetSeedEntryOptions()); - // If the value is null, it's likely because - if (cachedValue is null) + // If the value is null, it's likely because + if (cachedValue is null) { await _hybridCache.RemoveAsync(cacheKey); } } - - scope.Complete(); } private HybridCacheEntryOptions GetSeedEntryOptions() => new() @@ -257,6 +264,7 @@ public void Rebuild(IReadOnlyCollection contentTypeIds) using ICoreScope scope = _scopeProvider.CreateCoreScope(); _databaseCacheRepository.Rebuild(contentTypeIds.ToList()); IEnumerable contentByContentTypeKey = _databaseCacheRepository.GetContentByContentTypeKey(contentTypeIds.Select(x => _idKeyMap.GetKeyForId(x, UmbracoObjectTypes.DocumentType).Result), ContentCacheDataSerializerEntityType.Document); + scope.Complete(); foreach (ContentCacheNode content in contentByContentTypeKey) { @@ -268,6 +276,6 @@ public void Rebuild(IReadOnlyCollection contentTypeIds) } } - scope.Complete(); + } } diff --git a/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs b/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs index e1009173461b..024413f2c524 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs @@ -75,13 +75,16 @@ public MediaCacheService( return null; } - using ICoreScope scope = _scopeProvider.CreateCoreScope(); - ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync( $"{key}", // Unique key to the cache entry - async cancel => await _databaseCacheRepository.GetMediaSourceAsync(idAttempt.Result)); + async cancel => + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + ContentCacheNode? mediaCacheNode = await _databaseCacheRepository.GetMediaSourceAsync(idAttempt.Result); + scope.Complete(); + return mediaCacheNode; + }); - scope.Complete(); return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedMedia(contentCacheNode).CreateModel(_publishedModelFactory); } @@ -93,11 +96,16 @@ public MediaCacheService( return null; } - using ICoreScope scope = _scopeProvider.CreateCoreScope(); ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync( $"{keyAttempt.Result}", // Unique key to the cache entry - async cancel => await _databaseCacheRepository.GetMediaSourceAsync(id)); - scope.Complete(); + async cancel => + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + ContentCacheNode? mediaCacheNode = await _databaseCacheRepository.GetMediaSourceAsync(id); + scope.Complete(); + return mediaCacheNode; + }); + return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedMedia(contentCacheNode).CreateModel(_publishedModelFactory); } @@ -144,7 +152,6 @@ public async Task DeleteItemAsync(IContentBase media) public async Task SeedAsync(CancellationToken cancellationToken) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(); foreach (Guid key in SeedKeys) { @@ -157,7 +164,13 @@ public async Task SeedAsync(CancellationToken cancellationToken) ContentCacheNode? cachedValue = await _hybridCache.GetOrCreateAsync( cacheKey, - async cancel => await _databaseCacheRepository.GetMediaSourceAsync(key), + async cancel => + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + ContentCacheNode? mediaCacheNode = await _databaseCacheRepository.GetMediaSourceAsync(key); + scope.Complete(); + return mediaCacheNode; + }, GetSeedEntryOptions()); if (cachedValue is null) @@ -165,8 +178,6 @@ public async Task SeedAsync(CancellationToken cancellationToken) await _hybridCache.RemoveAsync(cacheKey); } } - - scope.Complete(); } public void Rebuild(IReadOnlyCollection contentTypeIds) diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 4ccaccfba541..22861429794e 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -194,8 +194,8 @@ public static IUmbracoBuilder AddRecurringBackgroundJobs(this IUmbracoBuilder bu builder.Services.AddSingleton(RecurringBackgroundJobHostedService.CreateHostedServiceFactory); builder.Services.AddHostedService(); builder.Services.AddHostedService(); - builder.Services.AddHostedService(); - builder.Services.AddHostedService(); + builder.AddNotificationAsyncHandler(); + builder.AddNotificationAsyncHandler(); return builder; } diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index ec4d0e2600a5..39e6c8ac044b 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -172,7 +172,7 @@ protected void ConfigureServices(IServiceCollection services) .AddCoreMappingProfiles(); } - services.RemoveAll(x=>x.ImplementationType == typeof(DocumentUrlServiceInitializer)); + services.RemoveAll(x=>x.ImplementationType == typeof(DocumentUrlServiceInitializerNotificationHandler)); services.AddSignalR(); services.AddMvc(); diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs index 2ff15c353991..77c65ab10b50 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs @@ -35,7 +35,7 @@ public abstract class UmbracoIntegrationTestWithContent : UmbracoIntegrationTest protected ContentType ContentType { get; private set; } [SetUp] - public void Setup() => CreateTestData(); + public virtual void Setup() => CreateTestData(); public virtual void CreateTestData() { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTest.cs index 5ef6de9a9249..585317d5b611 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTest.cs @@ -1,23 +1,13 @@ -using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Handlers; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -using Umbraco.Cms.Infrastructure.DependencyInjection; -using Umbraco.Cms.Infrastructure.Examine; -using Umbraco.Cms.Infrastructure.Examine.DependencyInjection; -using Umbraco.Cms.Infrastructure.HostedServices; -using Umbraco.Cms.Infrastructure.Search; -using Umbraco.Cms.Tests.Common.Attributes; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping; -using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; @@ -33,12 +23,15 @@ protected override void CustomTestSetup(IUmbracoBuilder builder) builder.Services.AddUnique(); builder.AddNotificationHandler(); - builder.Services.AddHostedService(); - + builder.Services.AddNotificationAsyncHandler(); } - + public override void Setup() + { + DocumentUrlService.InitAsync(false, CancellationToken.None).GetAwaiter().GetResult(); + base.Setup(); + } // // [Test] // [LongRunning] diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTest_hidetoplevel_false.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTest_hidetoplevel_false.cs index c38bd544bc70..41e3f1897983 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTest_hidetoplevel_false.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTest_hidetoplevel_false.cs @@ -29,8 +29,14 @@ protected override void CustomTestSetup(IUmbracoBuilder builder) builder.Services.AddUnique(); builder.AddNotificationHandler(); - builder.Services.AddHostedService(); } + + public override void Setup() + { + DocumentUrlService.InitAsync(false, CancellationToken.None).GetAwaiter().GetResult(); + base.Setup(); + } + [Test] [TestCase("/textpage/", "en-US", true, ExpectedResult = TextpageKey)] [TestCase("/textpage/text-page-1", "en-US", true, ExpectedResult = SubPageKey)] diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs index fd852afa86dc..3313d83cd389 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs @@ -5,13 +5,11 @@ using Moq; using NUnit.Framework; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Cms.Infrastructure.Examine.DependencyInjection; using Umbraco.Cms.Infrastructure.HostedServices; @@ -19,8 +17,6 @@ using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping; -using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; using Umbraco.Cms.Web.Common.Security; namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; @@ -37,7 +33,9 @@ public void Setup() var httpContext = new DefaultHttpContext(); httpContext.RequestServices = Services; Mock.Get(TestHelper.GetHttpContextAccessor()).Setup(x => x.HttpContext).Returns(httpContext); - } + + DocumentUrlService.InitAsync(false, CancellationToken.None).GetAwaiter().GetResult(); + } [TearDown] public void TearDown() @@ -48,6 +46,7 @@ public void TearDown() Services.DisposeIfDisposable(); } + private IDocumentUrlService DocumentUrlService => GetRequiredService(); private IBackOfficeExamineSearcher BackOfficeExamineSearcher => GetRequiredService(); private IContentTypeService ContentTypeService => GetRequiredService(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs index 66e9ad85fcec..4d2fb1530b34 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineBaseTest.cs @@ -44,9 +44,8 @@ protected override void CustomTestSetup(IUmbracoBuilder builder) builder .AddNotificationHandler(); - builder.Services.AddHostedService(); - } + /// /// Used to create and manage a testable index /// diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs index 24c04b643d48..3859f5fd1408 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexTests.cs @@ -37,6 +37,8 @@ public void Setup() var httpContext = new DefaultHttpContext(); httpContext.RequestServices = Services; Mock.Get(TestHelper.GetHttpContextAccessor()).Setup(x => x.HttpContext).Returns(httpContext); + + DocumentUrlService.InitAsync(false, CancellationToken.None).GetAwaiter().GetResult(); } [TearDown] @@ -52,6 +54,7 @@ public void TearDown() private IExamineExternalIndexSearcherTest ExamineExternalIndexSearcher => GetRequiredService(); + private IDocumentUrlService DocumentUrlService => GetRequiredService(); private IContentTypeService ContentTypeService => GetRequiredService(); private ContentService ContentService => (ContentService)GetRequiredService(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexTest.cs index 84819f89ac8c..856b0e8cf272 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexTest.cs @@ -3,6 +3,7 @@ using Lucene.Net.Util; using NUnit.Framework; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Cms.Tests.Common.Attributes; using Umbraco.Cms.Tests.Common.Builders; @@ -18,6 +19,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] public class IndexTest : ExamineBaseTest { + private IDocumentUrlService DocumentUrlService => GetRequiredService(); + + [SetUp] + public void Setup() + { + DocumentUrlService.InitAsync(false, CancellationToken.None).GetAwaiter().GetResult(); + } + + [Test] [LongRunning] public void GivenValidationParentNode_WhenContentIndexedUnderDifferentParent_DocumentIsNotIndexed() diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/SearchTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/SearchTests.cs index 2e360ca4cfb7..885c7d685115 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/SearchTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/SearchTests.cs @@ -18,6 +18,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] public class SearchTests : ExamineBaseTest { + private IDocumentUrlService DocumentUrlService => GetRequiredService(); + + [SetUp] + public void Setup() + { + DocumentUrlService.InitAsync(false, CancellationToken.None).GetAwaiter().GetResult(); + } + + [Test] [LongRunning] public void Test_Sort_Order_Sorting() diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs index 1e4ddd6f348d..f3ea130a68ac 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs @@ -52,14 +52,14 @@ public IScope CreateDetachedScope( public ISqlContext SqlContext { get; set; } + public IScope AmbientScope { get; } + #if DEBUG_SCOPES - public ScopeInfo GetScopeInfo(IScope scope) - { - throw new NotImplementedException(); - } - public IEnumerable ScopeInfos => throw new NotImplementedException(); + public IEnumerable ScopeInfos => throw new NotImplementedException(); + + public ScopeInfo GetScopeInfo(IScope scope) => throw new NotImplementedException(); #endif - public IScope AmbientScope { get; } + } private class TestPlan : MigrationPlan