Skip to content

Commit b722c0d

Browse files
Abstract submit and poll operations (#19688)
* Started implementing new LongRunningOperationService and adjusting tasks to use this service This service will manage operations that require status to be synced between servers (load balanced setup). * Missing migration to add new lock. Other simplifications. * Add job to cleanup the LongRunningOperations entries * Add new DatabaseCacheRebuilder.RebuildAsync method This is both async and returns an attempt, which will fail if a rebuild operation is already running. * Missing LongRunningOperation database table creation on clean install * Store expire date in the long running operation. Better handling of non-background operations. Storing an expiration date allows setting different expiration times depending on the type of operation, and whether it is running in the background or not. * Added integration tests for LongRunningOperationRepository * Added unit tests for LongRunningOperationService * Add type as a parameter to more repository calls. Distinguish between expiration and deletion in `LongRunningOperationRepository.CleanOperations`. * Fix failing unit test * Fixed `PerformPublishBranchAsync` result not being deserialized correctly * Remove unnecessary DatabaseCacheRebuildResult value * Add status to `LongRunningOperationService.GetResult` attempt to inform on why a result could not be retrieved * General improvements * Missing rename * Improve the handling of long running operations that are not in background and stale operations * Fix failing unit tests * Fixed small mismatch between interface and implementation * Use a fire and forget task instead of the background queue * Apply suggestions from code review Co-authored-by: Andy Butland <[email protected]> * Make sure exceptions are caught when running in the background * Alignment with other repositories (async + pagination) * Additional fixes * Add Async suffix to service methods * Missing adjustment * Moved hardcoded settings to IOptions * Fix issue in SQL Server where 0 is not accepted as requested number of rows * Fix issue in SQL Server where query provided to count cannot contain orderby * Additional SQL Server fixes --------- Co-authored-by: Andy Butland <[email protected]>
1 parent ca1476f commit b722c0d

File tree

42 files changed

+1891
-192
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1891
-192
lines changed

src/Umbraco.Cms.Api.Management/Controllers/Document/PublishDocumentWithDescendantsController.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ public async Task<IActionResult> PublishWithDescendants(CancellationToken cancel
5858
true);
5959

6060
return attempt.Success && attempt.Result.AcceptedTaskId.HasValue
61-
? Ok(new PublishWithDescendantsResultModel
62-
{
63-
TaskId = attempt.Result.AcceptedTaskId.Value,
64-
IsComplete = false
65-
})
61+
? Ok(
62+
new PublishWithDescendantsResultModel
63+
{
64+
TaskId = attempt.Result.AcceptedTaskId.Value,
65+
IsComplete = false,
66+
})
6667
: DocumentPublishingOperationStatusResult(attempt.Status, failedBranchItems: attempt.Result.FailedItems);
6768
}
6869

src/Umbraco.Cms.Api.Management/Controllers/Document/PublishDocumentWithDescendantsResultController.cs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,23 @@ public async Task<IActionResult> PublishWithDescendantsResult(CancellationToken
4949
var isPublishing = await _contentPublishingService.IsPublishingBranchAsync(taskId);
5050
if (isPublishing)
5151
{
52-
return Ok(new PublishWithDescendantsResultModel
53-
{
54-
TaskId = taskId,
55-
IsComplete = false
56-
});
57-
};
52+
return Ok(
53+
new PublishWithDescendantsResultModel
54+
{
55+
TaskId = taskId,
56+
IsComplete = false,
57+
});
58+
}
5859

5960
// If completed, get the result and return the status.
6061
Attempt<ContentPublishingBranchResult, ContentPublishingOperationStatus> attempt = await _contentPublishingService.GetPublishBranchResultAsync(taskId);
6162
return attempt.Success
62-
? Ok(new PublishWithDescendantsResultModel
63-
{
64-
TaskId = taskId,
65-
IsComplete = true
66-
})
63+
? Ok(
64+
new PublishWithDescendantsResultModel
65+
{
66+
TaskId = taskId,
67+
IsComplete = true,
68+
})
6769
: DocumentPublishingOperationStatusResult(attempt.Status, failedBranchItems: attempt.Result.FailedItems);
6870
}
6971
}

src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Asp.Versioning;
22
using Microsoft.AspNetCore.Http;
33
using Microsoft.AspNetCore.Mvc;
4+
using Umbraco.Cms.Core;
5+
using Umbraco.Cms.Core.Models;
46
using Umbraco.Cms.Core.PublishedCache;
57

68
namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
@@ -15,9 +17,10 @@ public class RebuildPublishedCacheController : PublishedCacheControllerBase
1517
[HttpPost("rebuild")]
1618
[MapToApiVersion("1.0")]
1719
[ProducesResponseType(StatusCodes.Status200OK)]
18-
public Task<IActionResult> Rebuild(CancellationToken cancellationToken)
20+
public async Task<IActionResult> Rebuild(CancellationToken cancellationToken)
1921
{
20-
if (_databaseCacheRebuilder.IsRebuilding())
22+
Attempt<DatabaseCacheRebuildResult> attempt = await _databaseCacheRebuilder.RebuildAsync(true);
23+
if (attempt is { Success: false, Result: DatabaseCacheRebuildResult.AlreadyRunning })
2124
{
2225
var problemDetails = new ProblemDetails
2326
{
@@ -26,11 +29,9 @@ public Task<IActionResult> Rebuild(CancellationToken cancellationToken)
2629
Status = StatusCodes.Status400BadRequest,
2730
Type = "Error",
2831
};
29-
30-
return Task.FromResult<IActionResult>(Conflict(problemDetails));
32+
return Conflict(problemDetails);
3133
}
3234

33-
_databaseCacheRebuilder.Rebuild(true);
34-
return Task.FromResult<IActionResult>(Ok());
35+
return Ok();
3536
}
3637
}

src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheStatusController.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ public class RebuildPublishedCacheStatusController : PublishedCacheControllerBas
1616
[HttpGet("rebuild/status")]
1717
[MapToApiVersion("1.0")]
1818
[ProducesResponseType(typeof(RebuildStatusModel), StatusCodes.Status200OK)]
19-
public Task<IActionResult> Status(CancellationToken cancellationToken)
19+
public async Task<IActionResult> Status(CancellationToken cancellationToken)
2020
{
21-
var isRebuilding = _databaseCacheRebuilder.IsRebuilding();
22-
return Task.FromResult((IActionResult)Ok(new RebuildStatusModel
23-
{
24-
IsRebuilding = isRebuilding
25-
}));
21+
var isRebuilding = await _databaseCacheRebuilder.IsRebuildingAsync();
22+
return Ok(
23+
new RebuildStatusModel
24+
{
25+
IsRebuilding = isRebuilding,
26+
});
2627
}
2728
}

src/Umbraco.Core/AttemptOfTResultTStatus.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Text.Json.Serialization;
2+
13
namespace Umbraco.Cms.Core;
24

35
/// <summary>
@@ -9,6 +11,7 @@ namespace Umbraco.Cms.Core;
911
public struct Attempt<TResult, TStatus>
1012
{
1113
// private - use Succeed() or Fail() methods to create attempts
14+
[JsonConstructor]
1215
private Attempt(bool success, TResult result, TStatus status, Exception? exception)
1316
{
1417
Success = success;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Umbraco.
2+
// See LICENSE for more details.
3+
4+
using System.ComponentModel;
5+
6+
namespace Umbraco.Cms.Core.Configuration.Models;
7+
8+
/// <summary>
9+
/// Typed configuration options for long-running operations cleanup settings.
10+
/// </summary>
11+
public class LongRunningOperationsCleanupSettings
12+
{
13+
private const string StaticPeriod = "00:02:00";
14+
private const string StaticMaxAge = "01:00:00";
15+
16+
/// <summary>
17+
/// Gets or sets a value for the period in which long-running operations are cleaned up.
18+
/// </summary>
19+
[DefaultValue(StaticPeriod)]
20+
public TimeSpan Period { get; set; } = TimeSpan.Parse(StaticPeriod);
21+
22+
/// <summary>
23+
/// Gets or sets the maximum time a long-running operation entry can exist, without being updated, before it is considered for cleanup.
24+
/// </summary>
25+
[DefaultValue(StaticMaxAge)]
26+
public TimeSpan MaxEntryAge { get; set; } = TimeSpan.Parse(StaticMaxAge);
27+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) Umbraco.
2+
// See LICENSE for more details.
3+
4+
using System.ComponentModel;
5+
6+
namespace Umbraco.Cms.Core.Configuration.Models;
7+
8+
/// <summary>
9+
/// Typed configuration options for long-running operations settings.
10+
/// </summary>
11+
[UmbracoOptions(Constants.Configuration.ConfigLongRunningOperations)]
12+
public class LongRunningOperationsSettings
13+
{
14+
private const string StaticExpirationTime = "00:05:00";
15+
private const string StaticTimeBetweenStatusChecks = "00:00:10";
16+
17+
/// <summary>
18+
/// Gets or sets the cleanup settings for long-running operations.
19+
/// </summary>
20+
public LongRunningOperationsCleanupSettings Cleanup { get; set; } = new();
21+
22+
/// <summary>
23+
/// Gets or sets the time after which a long-running operation is considered expired/stale, if not updated.
24+
/// </summary>
25+
[DefaultValue(StaticExpirationTime)]
26+
public TimeSpan ExpirationTime { get; set; } = TimeSpan.Parse(StaticExpirationTime);
27+
28+
/// <summary>
29+
/// Gets or sets the time between status checks for long-running operations.
30+
/// </summary>
31+
[DefaultValue(StaticTimeBetweenStatusChecks)]
32+
public TimeSpan TimeBetweenStatusChecks { get; set; } = TimeSpan.Parse(StaticTimeBetweenStatusChecks);
33+
}

src/Umbraco.Core/Constants-Configuration.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static class Configuration
4040
public const string ConfigExamine = ConfigPrefix + "Examine";
4141
public const string ConfigIndexing = ConfigPrefix + "Indexing";
4242
public const string ConfigLogging = ConfigPrefix + "Logging";
43+
public const string ConfigLongRunningOperations = ConfigPrefix + "LongRunningOperations";
4344
public const string ConfigMemberPassword = ConfigPrefix + "Security:MemberPassword";
4445
public const string ConfigModelsBuilder = ConfigPrefix + "ModelsBuilder";
4546
public const string ConfigModelsMode = ConfigModelsBuilder + ":ModelsMode";

src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
6969
.AddUmbracoOptions<ImagingSettings>()
7070
.AddUmbracoOptions<IndexingSettings>()
7171
.AddUmbracoOptions<LoggingSettings>()
72+
.AddUmbracoOptions<LongRunningOperationsSettings>()
7273
.AddUmbracoOptions<MemberPasswordConfigurationSettings>()
7374
.AddUmbracoOptions<NuCacheSettings>()
7475
.AddUmbracoOptions<RequestHandlerSettings>()

src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
using Umbraco.Cms.Core.Security;
3535
using Umbraco.Cms.Core.Services;
3636
using Umbraco.Cms.Core.Services.ContentTypeEditing;
37+
using Umbraco.Cms.Core.HostedServices;
3738
using Umbraco.Cms.Core.Preview;
3839
using Umbraco.Cms.Core.PublishedCache;
3940
using Umbraco.Cms.Core.PublishedCache.Internal;
@@ -341,6 +342,7 @@ private void AddCoreServices()
341342
Services.AddUnique<ILocalizedTextService>(factory => new LocalizedTextService(
342343
factory.GetRequiredService<Lazy<LocalizedTextServiceFileSources>>(),
343344
factory.GetRequiredService<ILogger<LocalizedTextService>>()));
345+
Services.AddUnique<ILongRunningOperationService, LongRunningOperationService>();
344346

345347
Services.AddUnique<IEntityXmlSerializer, EntityXmlSerializer>();
346348

0 commit comments

Comments
 (0)