diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs index cba2252991..f9093ffd3b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Indexer/RebuildIndexerController.cs @@ -26,9 +26,9 @@ public class RebuildIndexerController : IndexerControllerBase } /// - /// Rebuilds the index + /// Rebuilds the index. /// - /// + /// The name of the index to rebuild. /// [HttpPost("{indexName}/rebuild")] [MapToApiVersion("1.0")] @@ -36,7 +36,7 @@ public class RebuildIndexerController : IndexerControllerBase [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status200OK)] - public Task Rebuild(CancellationToken cancellationToken, string indexName) + public async Task Rebuild(CancellationToken cancellationToken, string indexName) { if (!_examineManager.TryGetIndex(indexName, out IIndex? index)) { @@ -48,7 +48,7 @@ public class RebuildIndexerController : IndexerControllerBase Type = "Error", }; - return Task.FromResult(NotFound(invalidModelProblem)); + return NotFound(invalidModelProblem); } if (!_indexingRebuilderService.CanRebuild(index.Name)) @@ -57,19 +57,19 @@ public class RebuildIndexerController : IndexerControllerBase { Title = "Could not validate the populator", Detail = - $"The index {index?.Name} could not be rebuilt because we could not validate its associated {typeof(IIndexPopulator)}", + $"The index {index.Name} could not be rebuilt because we could not validate its associated {typeof(IIndexPopulator)}", Status = StatusCodes.Status400BadRequest, Type = "Error", }; - return Task.FromResult(BadRequest(invalidModelProblem)); + return BadRequest(invalidModelProblem); } _logger.LogInformation("Rebuilding index '{IndexName}'", indexName); - if (_indexingRebuilderService.TryRebuild(index, indexName)) + if (await _indexingRebuilderService.TryRebuildAsync(index, indexName)) { - return Task.FromResult(Ok()); + return Ok(); } var problemDetails = new ProblemDetails @@ -80,6 +80,6 @@ public class RebuildIndexerController : IndexerControllerBase Type = "Error", }; - return Task.FromResult(Conflict(problemDetails)); + return Conflict(problemDetails); } } diff --git a/src/Umbraco.Cms.Api.Management/Factories/IIndexPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IIndexPresentationFactory.cs index 7c5529fab9..d7b5ed4ea4 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IIndexPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IIndexPresentationFactory.cs @@ -5,5 +5,8 @@ namespace Umbraco.Cms.Api.Management.Factories; public interface IIndexPresentationFactory { + [Obsolete("Use CreateAsync() instead. Scheduled for removal in v19.")] IndexResponseModel Create(IIndex index); + + Task CreateAsync(IIndex index) => Task.FromResult(Create(index)); } diff --git a/src/Umbraco.Cms.Api.Management/Factories/IndexPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IndexPresentationFactory.cs index 116e3f9743..c5fd7a3ce7 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IndexPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IndexPresentationFactory.cs @@ -28,21 +28,17 @@ public class IndexPresentationFactory : IIndexPresentationFactory _logger = logger; } - [Obsolete("Use the non obsolete method instead. Scheduled for removal in v17")] - public IndexPresentationFactory(IIndexDiagnosticsFactory indexDiagnosticsFactory, IIndexRebuilder indexRebuilder, IIndexingRebuilderService indexingRebuilderService) - :this( - indexDiagnosticsFactory, - indexRebuilder, - indexingRebuilderService, - StaticServiceProvider.Instance.GetRequiredService>()) - { - } - + /// + [Obsolete("Use CreateAsync() instead. Scheduled for removal in v19.")] public IndexResponseModel Create(IIndex index) + => CreateAsync(index).GetAwaiter().GetResult(); + + /// + public async Task CreateAsync(IIndex index) { var isCorrupt = !TryGetSearcherName(index, out var searcherName); - if (_indexingRebuilderService.IsRebuilding(index.Name)) + if (await _indexingRebuilderService.IsRebuildingAsync(index.Name)) { return new IndexResponseModel { @@ -63,7 +59,7 @@ public class IndexPresentationFactory : IIndexPresentationFactory var properties = new Dictionary(); - foreach (var property in indexDiag.Metadata) + foreach (KeyValuePair property in indexDiag.Metadata) { if (property.Value is null) { @@ -71,7 +67,7 @@ public class IndexPresentationFactory : IIndexPresentationFactory } else { - var propertyType = property.Value.GetType(); + Type propertyType = property.Value.GetType(); properties[property.Key] = propertyType.IsClass && !propertyType.IsArray ? property.Value?.ToString() : property.Value; } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs index 59d1c68614..80ea4fd2ff 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs @@ -82,7 +82,7 @@ public static partial class UmbracoBuilderExtensions builder.AddNotificationHandler(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); - builder.AddNotificationHandler(); + builder.AddNotificationAsyncHandler(); builder.AddNotificationHandler(); diff --git a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs index edf8deca86..4f03a1fbe3 100644 --- a/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/ExamineIndexRebuilder.cs @@ -6,18 +6,20 @@ using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Runtime; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Infrastructure.HostedServices; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Infrastructure.Models; namespace Umbraco.Cms.Infrastructure.Examine; internal class ExamineIndexRebuilder : IIndexRebuilder { - private readonly IBackgroundTaskQueue _backgroundTaskQueue; + private const string RebuildAllOperationTypeName = "RebuildAllExamineIndexes"; + private readonly IExamineManager _examineManager; private readonly ILogger _logger; private readonly IMainDom _mainDom; private readonly IEnumerable _populators; - private readonly object _rebuildLocker = new(); + private readonly ILongRunningOperationService _longRunningOperationService; private readonly IRuntimeState _runtimeState; /// @@ -29,16 +31,17 @@ internal class ExamineIndexRebuilder : IIndexRebuilder ILogger logger, IExamineManager examineManager, IEnumerable populators, - IBackgroundTaskQueue backgroundTaskQueue) + ILongRunningOperationService longRunningOperationService) { _mainDom = mainDom; _runtimeState = runtimeState; _logger = logger; _examineManager = examineManager; _populators = populators; - _backgroundTaskQueue = backgroundTaskQueue; + _longRunningOperationService = longRunningOperationService; } + /// public bool CanRebuild(string indexName) { if (!_examineManager.TryGetIndex(indexName, out IIndex index)) @@ -49,180 +52,149 @@ internal class ExamineIndexRebuilder : IIndexRebuilder return _populators.Any(x => x.IsRegistered(index)); } + /// + [Obsolete("Use RebuildIndexAsync() instead. Scheduled for removal in v19.")] public virtual void RebuildIndex(string indexName, TimeSpan? delay = null, bool useBackgroundThread = true) + => RebuildIndexAsync(indexName, delay, useBackgroundThread).GetAwaiter().GetResult(); + + /// + public virtual async Task> RebuildIndexAsync(string indexName, TimeSpan? delay = null, bool useBackgroundThread = true) { - if (delay == null) - { - delay = TimeSpan.Zero; - } + delay ??= TimeSpan.Zero; if (!CanRun()) { - return; + return Attempt.Fail(IndexRebuildResult.NotAllowedToRun); } - if (useBackgroundThread) - { - _logger.LogInformation("Starting async background thread for rebuilding index {indexName}.", indexName); - - _backgroundTaskQueue.QueueBackgroundWorkItem( - cancellationToken => - { - // Do not flow AsyncLocal to the child thread - using (ExecutionContext.SuppressFlow()) - { - Task.Run(() => RebuildIndex(indexName, delay.Value, cancellationToken)); - - // immediately return so the queue isn't waiting. - return Task.CompletedTask; - } - }); - } - else - { - RebuildIndex(indexName, delay.Value, CancellationToken.None); - } - } - - public virtual void RebuildIndexes(bool onlyEmptyIndexes, TimeSpan? delay = null, bool useBackgroundThread = true) - { - if (delay == null) - { - delay = TimeSpan.Zero; - } - - if (!CanRun()) - { - return; - } - - if (useBackgroundThread) - { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + Attempt attempt = await _longRunningOperationService.RunAsync( + GetRebuildOperationTypeName(indexName), + async ct => { - _logger.LogDebug($"Queuing background job for {nameof(RebuildIndexes)}."); - } + await RebuildIndex(indexName, delay.Value, ct); + return Task.CompletedTask; + }, + allowConcurrentExecution: false, + runInBackground: useBackgroundThread); - _backgroundTaskQueue.QueueBackgroundWorkItem( - cancellationToken => - { - // Do not flow AsyncLocal to the child thread - using (ExecutionContext.SuppressFlow()) - { - // This is a fire/forget task spawned by the background thread queue (which means we - // don't need to worry about ExecutionContext flowing). - Task.Run(() => RebuildIndexes(onlyEmptyIndexes, delay.Value, cancellationToken)); - - // immediately return so the queue isn't waiting. - return Task.CompletedTask; - } - }); - } - else + if (attempt.Success) { - RebuildIndexes(onlyEmptyIndexes, delay.Value, CancellationToken.None); + return Attempt.Succeed(IndexRebuildResult.Success); } + + return attempt.Status switch + { + LongRunningOperationEnqueueStatus.AlreadyRunning => Attempt.Fail(IndexRebuildResult.AlreadyRebuilding), + _ => Attempt.Fail(IndexRebuildResult.Unknown), + }; } + /// + [Obsolete("Use RebuildIndexesAsync() instead. Scheduled for removal in v19.")] + public virtual void RebuildIndexes(bool onlyEmptyIndexes, TimeSpan? delay = null, bool useBackgroundThread = true) + => RebuildIndexesAsync(onlyEmptyIndexes, delay, useBackgroundThread).GetAwaiter().GetResult(); + + /// + public virtual async Task> RebuildIndexesAsync(bool onlyEmptyIndexes, TimeSpan? delay = null, bool useBackgroundThread = true) + { + delay ??= TimeSpan.Zero; + + if (!CanRun()) + { + return Attempt.Fail(IndexRebuildResult.NotAllowedToRun); + } + + Attempt attempt = await _longRunningOperationService.RunAsync( + RebuildAllOperationTypeName, + async ct => + { + await RebuildIndexes(onlyEmptyIndexes, delay.Value, ct); + return Task.CompletedTask; + }, + allowConcurrentExecution: false, + runInBackground: useBackgroundThread); + + if (attempt.Success) + { + return Attempt.Succeed(IndexRebuildResult.Success); + } + + return attempt.Status switch + { + LongRunningOperationEnqueueStatus.AlreadyRunning => Attempt.Fail(IndexRebuildResult.AlreadyRebuilding), + _ => Attempt.Fail(IndexRebuildResult.Unknown), + }; + } + + /// + public async Task IsRebuildingAsync(string indexName) + => (await _longRunningOperationService.GetByTypeAsync(GetRebuildOperationTypeName(indexName), 0, 0)).Total != 0; + + private static string GetRebuildOperationTypeName(string indexName) => $"RebuildExamineIndex-{indexName}"; + private bool CanRun() => _mainDom.IsMainDom && _runtimeState.Level == RuntimeLevel.Run; - private void RebuildIndex(string indexName, TimeSpan delay, CancellationToken cancellationToken) + private async Task RebuildIndex(string indexName, TimeSpan delay, CancellationToken cancellationToken) { if (delay > TimeSpan.Zero) { - Thread.Sleep(delay); + await Task.Delay(delay, cancellationToken); } - try + if (!_examineManager.TryGetIndex(indexName, out IIndex index)) { - if (!Monitor.TryEnter(_rebuildLocker)) - { - _logger.LogWarning( - "Call was made to RebuildIndexes but the task runner for rebuilding is already running"); - } - else - { - if (!_examineManager.TryGetIndex(indexName, out IIndex index)) - { - throw new InvalidOperationException($"No index found with name {indexName}"); - } - - index.CreateIndex(); // clear the index - foreach (IIndexPopulator populator in _populators) - { - if (cancellationToken.IsCancellationRequested) - { - return; - } - - populator.Populate(index); - } - } + throw new InvalidOperationException($"No index found with name {indexName}"); } - finally + + index.CreateIndex(); // clear the index + foreach (IIndexPopulator populator in _populators) { - if (Monitor.IsEntered(_rebuildLocker)) + if (cancellationToken.IsCancellationRequested) { - Monitor.Exit(_rebuildLocker); + return; } + + populator.Populate(index); } } - private void RebuildIndexes(bool onlyEmptyIndexes, TimeSpan delay, CancellationToken cancellationToken) + private async Task RebuildIndexes(bool onlyEmptyIndexes, TimeSpan delay, CancellationToken cancellationToken) { if (delay > TimeSpan.Zero) { - Thread.Sleep(delay); + await Task.Delay(delay, cancellationToken); } - try + // If an index exists but it has zero docs we'll consider it empty and rebuild + IIndex[] indexes = (onlyEmptyIndexes + ? _examineManager.Indexes.Where(ShouldRebuild) + : _examineManager.Indexes).ToArray(); + + if (indexes.Length == 0) { - if (!Monitor.TryEnter(_rebuildLocker)) - { - _logger.LogWarning( - $"Call was made to {nameof(RebuildIndexes)} but the task runner for rebuilding is already running"); - } - else - { - // If an index exists but it has zero docs we'll consider it empty and rebuild - IIndex[] indexes = (onlyEmptyIndexes - ? _examineManager.Indexes.Where(ShouldRebuild) - : _examineManager.Indexes).ToArray(); - - if (indexes.Length == 0) - { - return; - } - - foreach (IIndex index in indexes) - { - index.CreateIndex(); // clear the index - } - - // run each populator over the indexes - foreach (IIndexPopulator populator in _populators) - { - if (cancellationToken.IsCancellationRequested) - { - return; - } - - try - { - populator.Populate(indexes); - } - catch (Exception e) - { - _logger.LogError(e, "Index populating failed for populator {Populator}", populator.GetType()); - } - } - } + return; } - finally + + foreach (IIndex index in indexes) { - if (Monitor.IsEntered(_rebuildLocker)) + index.CreateIndex(); // clear the index + } + + // run each populator over the indexes + foreach (IIndexPopulator populator in _populators) + { + if (cancellationToken.IsCancellationRequested) { - Monitor.Exit(_rebuildLocker); + return; + } + + try + { + populator.Populate(indexes); + } + catch (Exception e) + { + _logger.LogError(e, "Index populating failed for populator {Populator}", populator.GetType()); } } } diff --git a/src/Umbraco.Infrastructure/Examine/IIndexRebuilder.cs b/src/Umbraco.Infrastructure/Examine/IIndexRebuilder.cs index a85c551b8d..78574b01f2 100644 --- a/src/Umbraco.Infrastructure/Examine/IIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/IIndexRebuilder.cs @@ -1,10 +1,69 @@ +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Models; + namespace Umbraco.Cms.Infrastructure.Examine; +/// +/// Interface for rebuilding search indexes. +/// public interface IIndexRebuilder { + /// + /// Checks if the specified index can be rebuilt. + /// + /// The name of the index to check. + /// Whether the index can be rebuilt. bool CanRebuild(string indexName); + /// + /// Rebuilds the specified index. + /// + /// The name of the index to rebuild. + /// The delay before starting the rebuild. + /// Whether to use a background thread for the rebuild. + [Obsolete("Use RebuildIndexesAsync() instead. Scheduled for removal in V19.")] void RebuildIndex(string indexName, TimeSpan? delay = null, bool useBackgroundThread = true); + /// + /// Rebuilds the specified index. + /// + /// The name of the index to rebuild. + /// The delay before starting the rebuild. + /// Whether to use a background thread for the rebuild. + /// A task representing the asynchronous operation. + Task> RebuildIndexAsync(string indexName, TimeSpan? delay = null, bool useBackgroundThread = true) + { + RebuildIndex(indexName, delay, useBackgroundThread); + return Task.FromResult(Attempt.Succeed(IndexRebuildResult.Success)); + } + + /// + /// Rebuilds all indexes, or only those that are empty. + /// + /// Whether to only rebuild empty indexes. + /// The delay before starting the rebuild. + /// Whether to use a background thread for the rebuild. + [Obsolete("Use RebuildIndexesAsync() instead. Scheduled for removal in V19.")] void RebuildIndexes(bool onlyEmptyIndexes, TimeSpan? delay = null, bool useBackgroundThread = true); + + /// + /// Rebuilds all indexes, or only those that are empty. + /// + /// Whether to only rebuild empty indexes. + /// The delay before starting the rebuild. + /// Whether to use a background thread for the rebuild. + /// A task representing the asynchronous operation. + Task> RebuildIndexesAsync(bool onlyEmptyIndexes, TimeSpan? delay = null, bool useBackgroundThread = true) + { + RebuildIndexes(onlyEmptyIndexes, delay, useBackgroundThread); + return Task.FromResult(Attempt.Succeed(IndexRebuildResult.Success)); + } + + /// + /// Checks if the specified index is currently being rebuilt. + /// + /// The name of the index to check. + /// Whether the index is currently being rebuilt. + // TODO (v19): Remove the default implementation. + Task IsRebuildingAsync(string indexName) => throw new NotImplementedException(); } diff --git a/src/Umbraco.Infrastructure/Examine/NoopIndexRebuilder.cs b/src/Umbraco.Infrastructure/Examine/NoopIndexRebuilder.cs index bfa1c9f0f0..04f1efd59c 100644 --- a/src/Umbraco.Infrastructure/Examine/NoopIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/NoopIndexRebuilder.cs @@ -7,4 +7,6 @@ internal sealed class NoopIndexRebuilder : IIndexRebuilder public void RebuildIndex(string indexName, TimeSpan? delay = null, bool useBackgroundThread = true) {} public void RebuildIndexes(bool onlyEmptyIndexes, TimeSpan? delay = null, bool useBackgroundThread = true) {} + + public Task IsRebuildingAsync(string indexName) => Task.FromResult(false); } diff --git a/src/Umbraco.Infrastructure/Models/IndexRebuildResult.cs b/src/Umbraco.Infrastructure/Models/IndexRebuildResult.cs new file mode 100644 index 0000000000..2d34ca9522 --- /dev/null +++ b/src/Umbraco.Infrastructure/Models/IndexRebuildResult.cs @@ -0,0 +1,27 @@ +namespace Umbraco.Cms.Infrastructure.Models; + +/// +/// Represents the status of an index rebuild trigger. +/// +public enum IndexRebuildResult +{ + /// + /// The rebuild was either successful or enqueued successfully. + /// + Success, + + /// + /// The index is already being rebuilt. + /// + AlreadyRebuilding, + + /// + /// The index rebuild was not scheduled because it's not allowed to run at this time. + /// + NotAllowedToRun, + + /// + /// The index rebuild was not scheduled due to an unknown error. + /// + Unknown, +} diff --git a/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.Language.cs b/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.Language.cs index cf8a2e1ab3..7092050edf 100644 --- a/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.Language.cs +++ b/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.Language.cs @@ -5,7 +5,9 @@ using Umbraco.Cms.Infrastructure.Examine; namespace Umbraco.Cms.Infrastructure.Search; -public sealed class LanguageIndexingNotificationHandler : INotificationHandler +public sealed class LanguageIndexingNotificationHandler : + INotificationHandler, + INotificationAsyncHandler { private readonly IIndexRebuilder _indexRebuilder; private readonly IUmbracoIndexingHandler _umbracoIndexingHandler; @@ -19,14 +21,20 @@ public sealed class LanguageIndexingNotificationHandler : INotificationHandler + [Obsolete("Use HandleAsync instead. Scheduled for removal in V19.")] public void Handle(LanguageCacheRefresherNotification args) + => HandleAsync(args, CancellationToken.None).GetAwaiter().GetResult(); + + /// + public async Task HandleAsync(LanguageCacheRefresherNotification notification, CancellationToken cancellationToken) { if (!_umbracoIndexingHandler.Enabled) { return; } - if (!(args.MessageObject is LanguageCacheRefresher.JsonPayload[] payloads)) + if (notification.MessageObject is not LanguageCacheRefresher.JsonPayload[] payloads) { return; } @@ -37,14 +45,14 @@ public sealed class LanguageIndexingNotificationHandler : INotificationHandler - x.ChangeType == LanguageCacheRefresher.JsonPayload.LanguageChangeType.ChangeCulture - || x.ChangeType == LanguageCacheRefresher.JsonPayload.LanguageChangeType.Remove); + x.ChangeType is LanguageCacheRefresher.JsonPayload.LanguageChangeType.ChangeCulture + or LanguageCacheRefresher.JsonPayload.LanguageChangeType.Remove); if (removedOrCultureChanged) { // if a lang is removed or it's culture has changed, we need to rebuild the indexes since // field names and values in the index have a string culture value. - _indexRebuilder.RebuildIndexes(false); + _ = await _indexRebuilder.RebuildIndexesAsync(false); } } } diff --git a/src/Umbraco.Infrastructure/Services/IIndexingRebuilderService.cs b/src/Umbraco.Infrastructure/Services/IIndexingRebuilderService.cs index c23eb7115e..3f358c2bca 100644 --- a/src/Umbraco.Infrastructure/Services/IIndexingRebuilderService.cs +++ b/src/Umbraco.Infrastructure/Services/IIndexingRebuilderService.cs @@ -2,10 +2,48 @@ namespace Umbraco.Cms.Infrastructure.Services; +/// +/// Indexing rebuilder service. +/// public interface IIndexingRebuilderService { + /// + /// Checks if the index can be rebuilt. + /// + /// The name of the index to check. + /// Whether the index can be rebuilt. bool CanRebuild(string indexName); + + /// + /// Tries to rebuild the specified index. + /// + /// The index to rebuild. + /// The name of the index to rebuild. + /// Whether the rebuild was successfully scheduled. + [Obsolete("Use TryRebuildAsync() instead. Scheduled for removal in V19.")] bool TryRebuild(IIndex index, string indexName); + /// + /// Tries to rebuild the specified index. + /// + /// The index to rebuild. + /// The name of the index to rebuild. + /// Whether the rebuild was successfully scheduled. + Task TryRebuildAsync(IIndex index, string indexName) => Task.FromResult(TryRebuild(index, indexName)); + + /// + /// Checks if the specified index is currently being rebuilt. + /// + /// The name of the index to check. + /// Whether the index is currently being rebuilt. + [Obsolete("Use IsRebuildingAsync() instead. Scheduled for removal in V19.")] bool IsRebuilding(string indexName); + + /// + /// Checks if the specified index is currently being rebuilt. + /// + /// The name of the index to rebuild. + /// Whether the index is currently being rebuilt. + Task IsRebuildingAsync(string indexName) => + Task.FromResult(IsRebuilding(indexName)); } diff --git a/src/Umbraco.Infrastructure/Services/IndexingRebuilderService.cs b/src/Umbraco.Infrastructure/Services/IndexingRebuilderService.cs index c014277b6b..1263f52e5e 100644 --- a/src/Umbraco.Infrastructure/Services/IndexingRebuilderService.cs +++ b/src/Umbraco.Infrastructure/Services/IndexingRebuilderService.cs @@ -1,18 +1,27 @@ using Examine; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Cms.Infrastructure.Models; namespace Umbraco.Cms.Infrastructure.Services; +/// public class IndexingRebuilderService : IIndexingRebuilderService { - private const string IsRebuildingIndexRuntimeCacheKeyPrefix = "temp_indexing_op_"; - - private readonly IAppPolicyCache _runtimeCache; private readonly IIndexRebuilder _indexRebuilder; private readonly ILogger _logger; + public IndexingRebuilderService( + IIndexRebuilder indexRebuilder, + ILogger logger) + { + _indexRebuilder = indexRebuilder; + _logger = logger; + } + + [Obsolete("Use the non-obsolete constructor instead. Scheduled for removal in V19.")] public IndexingRebuilderService( AppCaches runtimeCache, IIndexRebuilder indexRebuilder, @@ -20,12 +29,18 @@ public class IndexingRebuilderService : IIndexingRebuilderService { _indexRebuilder = indexRebuilder; _logger = logger; - _runtimeCache = runtimeCache.RuntimeCache; } + /// public bool CanRebuild(string indexName) => _indexRebuilder.CanRebuild(indexName); + /// + [Obsolete("Use TryRebuildAsync instead. Scheduled for removal in V19.")] public bool TryRebuild(IIndex index, string indexName) + => TryRebuildAsync(index, indexName).GetAwaiter().GetResult(); + + /// + public async Task TryRebuildAsync(IIndex index, string indexName) { // Remove it in case there's a handler there already index.IndexOperationComplete -= Indexer_IndexOperationComplete; @@ -35,11 +50,10 @@ public class IndexingRebuilderService : IIndexingRebuilderService try { - Set(indexName); - _indexRebuilder.RebuildIndex(indexName); - return true; + Attempt attempt = await _indexRebuilder.RebuildIndexAsync(indexName); + return attempt.Success; } - catch(Exception exception) + catch (Exception exception) { // Ensure it's not listening index.IndexOperationComplete -= Indexer_IndexOperationComplete; @@ -48,25 +62,14 @@ public class IndexingRebuilderService : IIndexingRebuilderService } } - private void Set(string indexName) - { - var cacheKey = IsRebuildingIndexRuntimeCacheKeyPrefix + indexName; - - // put temp val in cache which is used as a rudimentary way to know when the indexing is done - _runtimeCache.Insert(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5)); - } - - private void Clear(string? indexName) - { - var cacheKey = IsRebuildingIndexRuntimeCacheKeyPrefix + indexName; - _runtimeCache.Clear(cacheKey); - } - + /// + [Obsolete("Use IsRebuildingAsync() instead. Scheduled for removal in V19.")] public bool IsRebuilding(string indexName) - { - var cacheKey = IsRebuildingIndexRuntimeCacheKeyPrefix + indexName; - return _runtimeCache.Get(cacheKey) is not null; - } + => IsRebuildingAsync(indexName).GetAwaiter().GetResult(); + + /// + public Task IsRebuildingAsync(string indexName) + => _indexRebuilder.IsRebuildingAsync(indexName); private void Indexer_IndexOperationComplete(object? sender, EventArgs e) { @@ -80,8 +83,6 @@ public class IndexingRebuilderService : IIndexingRebuilderService indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; } - _logger.LogInformation($"Rebuilding index '{indexer?.Name}' done."); - - Clear(indexer?.Name); + _logger.LogInformation("Rebuilding index '{IndexerName}' done.", indexer?.Name); } } diff --git a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs index 021eaaa2ba..958d328c1b 100644 --- a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs @@ -158,14 +158,14 @@ public static class UmbracoBuilderExtensions ILogger logger, IExamineManager examineManager, IEnumerable populators, - IBackgroundTaskQueue backgroundTaskQueue) + ILongRunningOperationService longRunningOperationService) : base( mainDom, runtimeState, logger, examineManager, populators, - backgroundTaskQueue) + longRunningOperationService) { }