Skip to content

Abstract submit and poll operations #19688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
65d6631
Started implementing new LongRunningOperationService and adjusting ta…
lauraneto Jul 3, 2025
c1c45b9
Missing migration to add new lock. Other simplifications.
lauraneto Jul 3, 2025
f14e793
Add job to cleanup the LongRunningOperations entries
lauraneto Jul 3, 2025
ffc2a95
Add new DatabaseCacheRebuilder.RebuildAsync method
lauraneto Jul 3, 2025
1ff1e8f
Missing LongRunningOperation database table creation on clean install
lauraneto Jul 4, 2025
bd7e4eb
Store expire date in the long running operation. Better handling of n…
lauraneto Jul 4, 2025
155a0ed
Added integration tests for LongRunningOperationRepository
lauraneto Jul 4, 2025
b6c6ffd
Added unit tests for LongRunningOperationService
lauraneto Jul 4, 2025
0d99a47
Add type as a parameter to more repository calls. Distinguish between…
lauraneto Jul 7, 2025
4d62797
Fix failing unit test
lauraneto Jul 8, 2025
4d07d75
Fixed `PerformPublishBranchAsync` result not being deserialized corre…
lauraneto Jul 8, 2025
76c80a0
Remove unnecessary DatabaseCacheRebuildResult value
lauraneto Jul 8, 2025
121eb08
Add status to `LongRunningOperationService.GetResult` attempt to info…
lauraneto Jul 8, 2025
6583464
Merge branch 'main' into v16/feature/abstract-submit-and-poll-operati…
lauraneto Jul 8, 2025
d6611ef
General improvements
lauraneto Jul 9, 2025
9ee944e
Missing rename
lauraneto Jul 9, 2025
afa3dde
Improve the handling of long running operations that are not in backg…
lauraneto Jul 9, 2025
a5a51cf
Merge branch 'main' into v16/feature/abstract-submit-and-poll-operati…
lauraneto Jul 9, 2025
60c7ddc
Fix failing unit tests
lauraneto Jul 9, 2025
c536c37
Fixed small mismatch between interface and implementation
lauraneto Jul 10, 2025
adc90a2
Use a fire and forget task instead of the background queue
lauraneto Jul 10, 2025
e7b234e
Apply suggestions from code review
lauraneto Jul 10, 2025
d4cde0e
Make sure exceptions are caught when running in the background
lauraneto Jul 10, 2025
ce99723
Alignment with other repositories (async + pagination)
lauraneto Jul 10, 2025
14a5a91
Additional fixes
lauraneto Jul 10, 2025
285580d
Add Async suffix to service methods
lauraneto Jul 10, 2025
c33e7a6
Missing adjustment
lauraneto Jul 10, 2025
7c572e5
Moved hardcoded settings to IOptions
lauraneto Jul 11, 2025
ec47efb
Fix issue in SQL Server where 0 is not accepted as requested number o…
lauraneto Jul 11, 2025
f3c41e4
Fix issue in SQL Server where query provided to count cannot contain …
lauraneto Jul 11, 2025
c3ce6b7
Additional SQL Server fixes
lauraneto Jul 14, 2025
a8b7989
Merge branch 'main' into v16/feature/abstract-submit-and-poll-operati…
lauraneto Jul 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ public async Task<IActionResult> PublishWithDescendants(CancellationToken cancel
true);

return attempt.Success && attempt.Result.AcceptedTaskId.HasValue
? Ok(new PublishWithDescendantsResultModel
{
TaskId = attempt.Result.AcceptedTaskId.Value,
IsComplete = false
})
? Ok(
new PublishWithDescendantsResultModel
{
TaskId = attempt.Result.AcceptedTaskId.Value,
IsComplete = false,
})
: DocumentPublishingOperationStatusResult(attempt.Status, failedBranchItems: attempt.Result.FailedItems);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,23 @@ public async Task<IActionResult> PublishWithDescendantsResult(CancellationToken
var isPublishing = await _contentPublishingService.IsPublishingBranchAsync(taskId);
if (isPublishing)
{
return Ok(new PublishWithDescendantsResultModel
{
TaskId = taskId,
IsComplete = false
});
};
return Ok(
new PublishWithDescendantsResultModel
{
TaskId = taskId,
IsComplete = false,
});
}

// If completed, get the result and return the status.
Attempt<ContentPublishingBranchResult, ContentPublishingOperationStatus> attempt = await _contentPublishingService.GetPublishBranchResultAsync(taskId);
return attempt.Success
? Ok(new PublishWithDescendantsResultModel
{
TaskId = taskId,
IsComplete = true
})
? Ok(
new PublishWithDescendantsResultModel
{
TaskId = taskId,
IsComplete = true,
})
: DocumentPublishingOperationStatusResult(attempt.Status, failedBranchItems: attempt.Result.FailedItems);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PublishedCache;

namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
Expand All @@ -15,9 +17,10 @@ public class RebuildPublishedCacheController : PublishedCacheControllerBase
[HttpPost("rebuild")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<IActionResult> Rebuild(CancellationToken cancellationToken)
public async Task<IActionResult> Rebuild(CancellationToken cancellationToken)
{
if (_databaseCacheRebuilder.IsRebuilding())
Attempt<DatabaseCacheRebuildResult> attempt = await _databaseCacheRebuilder.RebuildAsync(true);
if (attempt is { Success: false, Result: DatabaseCacheRebuildResult.AlreadyRunning })
{
var problemDetails = new ProblemDetails
{
Expand All @@ -26,11 +29,9 @@ public Task<IActionResult> Rebuild(CancellationToken cancellationToken)
Status = StatusCodes.Status400BadRequest,
Type = "Error",
};

return Task.FromResult<IActionResult>(Conflict(problemDetails));
return Conflict(problemDetails);
}

_databaseCacheRebuilder.Rebuild(true);
return Task.FromResult<IActionResult>(Ok());
return Ok();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ public class RebuildPublishedCacheStatusController : PublishedCacheControllerBas
[HttpGet("rebuild/status")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(RebuildStatusModel), StatusCodes.Status200OK)]
public Task<IActionResult> Status(CancellationToken cancellationToken)
public async Task<IActionResult> Status(CancellationToken cancellationToken)
{
var isRebuilding = _databaseCacheRebuilder.IsRebuilding();
return Task.FromResult((IActionResult)Ok(new RebuildStatusModel
{
IsRebuilding = isRebuilding
}));
var isRebuilding = await _databaseCacheRebuilder.IsRebuildingAsync();
return Ok(
new RebuildStatusModel
{
IsRebuilding = isRebuilding,
});
}
}
3 changes: 3 additions & 0 deletions src/Umbraco.Core/AttemptOfTResultTStatus.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;

namespace Umbraco.Cms.Core;

/// <summary>
Expand All @@ -9,6 +11,7 @@ namespace Umbraco.Cms.Core;
public struct Attempt<TResult, TStatus>
{
// private - use Succeed() or Fail() methods to create attempts
[JsonConstructor]
private Attempt(bool success, TResult result, TStatus status, Exception? exception)
{
Success = success;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using System.ComponentModel;

namespace Umbraco.Cms.Core.Configuration.Models;

/// <summary>
/// Typed configuration options for long-running operations cleanup settings.
/// </summary>
public class LongRunningOperationsCleanupSettings
{
private const string StaticPeriod = "00:02:00";
private const string StaticMaxAge = "01:00:00";

/// <summary>
/// Gets or sets a value for the period in which long-running operations are cleaned up.
/// </summary>
[DefaultValue(StaticPeriod)]
public TimeSpan Period { get; set; } = TimeSpan.Parse(StaticPeriod);

/// <summary>
/// Gets or sets the maximum time a long-running operation entry can exist, without being updated, before it is considered for cleanup.
/// </summary>
[DefaultValue(StaticMaxAge)]
public TimeSpan MaxEntryAge { get; set; } = TimeSpan.Parse(StaticMaxAge);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using System.ComponentModel;

namespace Umbraco.Cms.Core.Configuration.Models;

/// <summary>
/// Typed configuration options for long-running operations settings.
/// </summary>
[UmbracoOptions(Constants.Configuration.ConfigLongRunningOperations)]
public class LongRunningOperationsSettings
{
private const string StaticExpirationTime = "00:05:00";
private const string StaticTimeBetweenStatusChecks = "00:00:10";

/// <summary>
/// Gets or sets the cleanup settings for long-running operations.
/// </summary>
public LongRunningOperationsCleanupSettings Cleanup { get; set; } = new();

/// <summary>
/// Gets or sets the time after which a long-running operation is considered expired/stale, if not updated.
/// </summary>
[DefaultValue(StaticExpirationTime)]
public TimeSpan ExpirationTime { get; set; } = TimeSpan.Parse(StaticExpirationTime);

/// <summary>
/// Gets or sets the time between status checks for long-running operations.
/// </summary>
[DefaultValue(StaticTimeBetweenStatusChecks)]
public TimeSpan TimeBetweenStatusChecks { get; set; } = TimeSpan.Parse(StaticTimeBetweenStatusChecks);
}
1 change: 1 addition & 0 deletions src/Umbraco.Core/Constants-Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static class Configuration
public const string ConfigExamine = ConfigPrefix + "Examine";
public const string ConfigIndexing = ConfigPrefix + "Indexing";
public const string ConfigLogging = ConfigPrefix + "Logging";
public const string ConfigLongRunningOperations = ConfigPrefix + "LongRunningOperations";
public const string ConfigMemberPassword = ConfigPrefix + "Security:MemberPassword";
public const string ConfigModelsBuilder = ConfigPrefix + "ModelsBuilder";
public const string ConfigModelsMode = ConfigModelsBuilder + ":ModelsMode";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
.AddUmbracoOptions<ImagingSettings>()
.AddUmbracoOptions<IndexingSettings>()
.AddUmbracoOptions<LoggingSettings>()
.AddUmbracoOptions<LongRunningOperationsSettings>()
.AddUmbracoOptions<MemberPasswordConfigurationSettings>()
.AddUmbracoOptions<NuCacheSettings>()
.AddUmbracoOptions<RequestHandlerSettings>()
Expand Down
2 changes: 2 additions & 0 deletions src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.ContentTypeEditing;
using Umbraco.Cms.Core.HostedServices;
using Umbraco.Cms.Core.Preview;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.PublishedCache.Internal;
Expand Down Expand Up @@ -341,6 +342,7 @@
Services.AddUnique<ILocalizedTextService>(factory => new LocalizedTextService(
factory.GetRequiredService<Lazy<LocalizedTextServiceFileSources>>(),
factory.GetRequiredService<ILogger<LocalizedTextService>>()));
Services.AddUnique<ILongRunningOperationService, LongRunningOperationService>();

Check warning on line 345 in src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ Getting worse: Large Method

AddCoreServices increases from 205 to 206 lines of code, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.

Services.AddUnique<IEntityXmlSerializer, EntityXmlSerializer>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Umbraco.Cms.Core.Models.ContentPublishing;

internal sealed class ContentPublishingBranchInternalResult
{
public Guid? ContentKey { get; init; }

public IContent? Content { get; init; }

public IEnumerable<ContentPublishingBranchItemResult> SucceededItems { get; set; } = [];

public IEnumerable<ContentPublishingBranchItemResult> FailedItems { get; set; } = [];

public Guid? AcceptedTaskId { get; init; }
}
17 changes: 17 additions & 0 deletions src/Umbraco.Core/Models/DatabaseCacheRebuildResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Umbraco.Cms.Core.Models;

/// <summary>
/// Represents the result of a database cache rebuild operation.
/// </summary>
public enum DatabaseCacheRebuildResult
{
/// <summary>
/// The cache rebuild operation was either successful or enqueued successfully.
/// </summary>
Success,

/// <summary>
/// A cache rebuild operation is already in progress.
/// </summary>
AlreadyRunning,
}
22 changes: 22 additions & 0 deletions src/Umbraco.Core/Models/LongRunningOperation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Umbraco.Cms.Core.Models;

/// <summary>
/// Represents a long-running operation.
/// </summary>
public class LongRunningOperation
{
/// <summary>
/// Gets the unique identifier for the long-running operation.
/// </summary>
public required Guid Id { get; init; }

/// <summary>
/// Gets or sets the type of the long-running operation.
/// </summary>
public required string Type { get; set; }

/// <summary>
/// Gets or sets the status of the long-running operation.
/// </summary>
public required LongRunningOperationStatus Status { get; set; }
}
13 changes: 13 additions & 0 deletions src/Umbraco.Core/Models/LongRunningOperationOfTResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Umbraco.Cms.Core.Models;

/// <summary>
/// Represents a long-running operation.
/// </summary>
/// <typeparam name="TResult">The type of the result of the long-running operation.</typeparam>
public class LongRunningOperation<TResult> : LongRunningOperation
{
/// <summary>
/// Gets or sets the result of the long-running operation.
/// </summary>
public TResult? Result { get; set; }
}
32 changes: 32 additions & 0 deletions src/Umbraco.Core/Models/LongRunningOperationStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Umbraco.Cms.Core.Models;

/// <summary>
/// Represents the status of a long-running operation.
/// </summary>
public enum LongRunningOperationStatus
{
/// <summary>
/// The operation has finished successfully.
/// </summary>
Success,

/// <summary>
/// The operation has failed.
/// </summary>
Failed,

/// <summary>
/// The operation has been queued.
/// </summary>
Enqueued,

/// <summary>
/// The operation is currently running.
/// </summary>
Running,

/// <summary>
/// The operation wasn't updated within the expected time frame and is considered stale.
/// </summary>
Stale,
}
2 changes: 2 additions & 0 deletions src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ public static class Tables
public const string Webhook2Headers = Webhook + "2Headers";
public const string WebhookLog = Webhook + "Log";
public const string WebhookRequest = Webhook + "Request";

public const string LongRunningOperation = TableNamePrefix + "LongRunningOperation";
}
}
}
5 changes: 5 additions & 0 deletions src/Umbraco.Core/Persistence/Constants-Locks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,10 @@ public static class Locks
/// All webhook logs.
/// </summary>
public const int WebhookLogs = -343;

/// <summary>
/// Long-running operations.
/// </summary>
public const int LongRunningOperations = -344;
}
}
Loading