Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a8a47ca
Start work
Zeegaan Sep 30, 2025
54cba3d
Introduce dto
Zeegaan Sep 30, 2025
d0cd5bf
Start making repository
Zeegaan Sep 30, 2025
6278ebd
Add migrations
Zeegaan Oct 1, 2025
d23a9c6
Implement fetchable first job
Zeegaan Oct 2, 2025
db7e648
Fix up to also finish tasks
Zeegaan Oct 2, 2025
c40a964
Refactor jobs to distributed background jobs
Zeegaan Oct 6, 2025
9e0dc52
Filter jobs correctly on LastRun
Zeegaan Oct 6, 2025
40b38c7
Hardcode delay
Zeegaan Oct 6, 2025
d97fde1
Add settings to configure delay and period
Zeegaan Oct 6, 2025
6890d90
Fix formatting
Zeegaan Oct 6, 2025
b674d7a
Add default data
Zeegaan Oct 6, 2025
876c888
Add update on startup, which will update periods on startup
Zeegaan Oct 6, 2025
5206597
Refactor service to return job directly
Zeegaan Oct 7, 2025
a6d86e8
Update src/Umbraco.Infrastructure/Services/Implement/DistributedJobSe…
Zeegaan Oct 7, 2025
a91a41c
Update src/Umbraco.Infrastructure/BackgroundJobs/DistributedBackgroun…
Zeegaan Oct 7, 2025
f338f05
Update src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCrea…
Zeegaan Oct 7, 2025
4a350d6
Update src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCrea…
Zeegaan Oct 7, 2025
47309d7
Update src/Umbraco.Infrastructure/BackgroundJobs/DistributedBackgroun…
Zeegaan Oct 7, 2025
ef6a3d4
Remove unused
Zeegaan Oct 7, 2025
e75c9c6
Move jobs and make internal
Zeegaan Oct 7, 2025
21edd63
make OpenIddictCleanupJob.cs public, as it is used elsewhere
Zeegaan Oct 7, 2025
1e9aba9
Minor docstring changes
nikolajlauridsen Oct 7, 2025
45458ae
Update src/Umbraco.Core/Persistence/Constants-Locks.cs
Zeegaan Oct 7, 2025
7372a87
´Throw correct exceptions
Zeegaan Oct 7, 2025
138f250
Update xml doc
Zeegaan Oct 7, 2025
6064a76
Remove business logic from repository
Zeegaan Oct 7, 2025
874a6f9
Remove more business logic from repository into service
Zeegaan Oct 7, 2025
03ddb53
Remove adding jobs from migration
Zeegaan Oct 7, 2025
3d537dd
fix creation
Zeegaan Oct 7, 2025
641ff91
Rename to ExecuteAsync
Zeegaan Oct 7, 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 @@ -9,8 +9,8 @@
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Infrastructure.BackgroundJobs;
using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
using Umbraco.Extensions;

namespace Umbraco.Cms.Api.Common.DependencyInjection;

Expand Down Expand Up @@ -139,7 +139,7 @@ private static void ConfigureOpenIddict(IUmbracoBuilder builder)
});
});

builder.Services.AddRecurringBackgroundJob<OpenIddictCleanupJob>();
builder.Services.AddSingleton<IDistributedBackgroundJob, OpenIddictCleanupJob>();
builder.Services.ConfigureOptions<ConfigureOpenIddict>();
}
}
25 changes: 25 additions & 0 deletions src/Umbraco.Core/Configuration/Models/DistributedJobSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.ComponentModel;

namespace Umbraco.Cms.Core.Configuration.Models;

/// <summary>
/// Settings for distributed jobs.
/// </summary>
[UmbracoOptions(Constants.Configuration.ConfigDistributedJobs)]
public class DistributedJobSettings
{
internal const string StaticPeriod = "00:00:10";
internal const string StaticDelay = "00:01:00";

/// <summary>
/// Gets or sets a value for the period of checking if there are any runnable distributed jobs.
/// </summary>
[DefaultValue(StaticPeriod)]
public TimeSpan Period { get; set; } = TimeSpan.Parse(StaticPeriod);

/// <summary>
/// Gets or sets a value for the delay of when to start checking for distributed jobs.
/// </summary>
[DefaultValue(StaticDelay)]
public TimeSpan Delay { get; set; } = TimeSpan.Parse(StaticDelay);
}
1 change: 1 addition & 0 deletions src/Umbraco.Core/Constants-Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public static class Configuration
public const string ConfigWebhook = ConfigPrefix + "Webhook";
public const string ConfigWebhookPayloadType = ConfigWebhook + ":PayloadType";
public const string ConfigCache = ConfigPrefix + "Cache";
public const string ConfigDistributedJobs = ConfigPrefix + "DistributedJobs";

public static class NamedOptions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
.AddUmbracoOptions<DataTypesSettings>()
.AddUmbracoOptions<WebhookSettings>()
.AddUmbracoOptions<CacheSettings>()
.AddUmbracoOptions<SystemDateMigrationSettings>();
.AddUmbracoOptions<SystemDateMigrationSettings>()
.AddUmbracoOptions<DistributedJobSettings>();

// Configure connection string and ensure it's updated when the configuration changes
builder.Services.AddSingleton<IConfigureOptions<ConnectionStrings>, ConfigureConnectionStrings>();
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,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";
public const string DistributedJob = TableNamePrefix + "DistributedJob";
}
}
}
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 @@ -90,5 +90,10 @@ public static class Locks
/// All document URLs.
/// </summary>
public const int DocumentUrls = -345;

/// <summary>
/// All distributed jobs.
/// </summary>
public const int DistributedJobs = -346;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System.Diagnostics;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Services;

namespace Umbraco.Cms.Infrastructure.BackgroundJobs;

/// <summary>
/// A hosted service that checks for any runnable distributed background jobs on a timer.
/// </summary>
public class DistributedBackgroundJobHostedService : BackgroundService
{
private readonly ILogger<DistributedBackgroundJobHostedService> _logger;
private readonly IRuntimeState _runtimeState;
private readonly IDistributedJobService _distributedJobService;
private DistributedJobSettings _distributedJobSettings;

/// <summary>
/// Initializes a new instance of the <see cref="DistributedBackgroundJobHostedService"/> class.
/// </summary>
/// <param name="logger"></param>
/// <param name="runtimeState"></param>
/// <param name="distributedJobService"></param>
/// <param name="distributedJobSettings"></param>
public DistributedBackgroundJobHostedService(
ILogger<DistributedBackgroundJobHostedService> logger,
IRuntimeState runtimeState,
IDistributedJobService distributedJobService,
IOptionsMonitor<DistributedJobSettings> distributedJobSettings)
{
_logger = logger;
_runtimeState = runtimeState;
_distributedJobService = distributedJobService;
_distributedJobSettings = distributedJobSettings.CurrentValue;
distributedJobSettings.OnChange(options =>
{
_distributedJobSettings = options;
});
}

/// <inheritdoc />
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Delay(_distributedJobSettings.Delay, stoppingToken);
// Update all jobs, periods might have changed when restarting.
await _distributedJobService.UpdateAllChangedAsync();

using PeriodicTimer timer = new(_distributedJobSettings.Period);

try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await RunRunnableJob();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}

private async Task RunRunnableJob()
{
// Do not run distributed jobs if we aren't in Run level, as we might not have booted properly.
if (_runtimeState.Level != RuntimeLevel.Run)
{
return;
}

IDistributedBackgroundJob? job = await _distributedJobService.TryTakeRunnableAsync();

if (job is null)
{
// No runnable jobs for now, return
return;
}

try
{
await job.RunJobAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "An exception occurred while running distributed background job '{JobName}'.", job.Name);
}
finally
{
try
{
await _distributedJobService.FinishAsync(job.Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "An exception occurred while finishing distributed background job '{JobName}'.", job.Name);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Umbraco.Cms.Infrastructure.BackgroundJobs;

/// <summary>
/// A background job that will be executed by an available server. With a single server setup this will always be the same.
/// With a load balanced setup, the executing server might change every time this needs to be executed.
/// </summary>
public interface IDistributedBackgroundJob
{
/// <summary>
/// Name of the job
/// </summary>
string Name { get; }

/// <summary>
/// Timespan representing how often the task should recur.
/// </summary>
TimeSpan Period { get; }

/// <summary>
/// Run the job.
/// </summary>
/// <returns></returns>
Task RunJobAsync();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Infrastructure.Scoping;

namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;

/// <summary>
/// A background job that prunes cache instructions from the database.
/// </summary>
public class CacheInstructionsPruningJob : IRecurringBackgroundJob
public class CacheInstructionsPruningJob : IDistributedBackgroundJob
{
private readonly IOptions<GlobalSettings> _globalSettings;
private readonly ICacheInstructionRepository _cacheInstructionRepository;
Expand All @@ -36,12 +35,7 @@ public CacheInstructionsPruningJob(
Period = globalSettings.Value.DatabaseServerMessenger.TimeBetweenPruneOperations;
}

/// <inheritdoc />
public event EventHandler PeriodChanged
{
add { }
remove { }
}
public string Name => "CacheInstructionsPruningJob";

/// <inheritdoc />
public TimeSpan Period { get; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Runtime;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Infrastructure.BackgroundJobs;

namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;

/// <summary>
/// Recurring hosted service that executes the content history cleanup.
/// </summary>
public class ContentVersionCleanupJob : IRecurringBackgroundJob
public class ContentVersionCleanupJob : IDistributedBackgroundJob
{
/// <inheritdoc />
public string Name => "ContentVersionCleanupJob";

/// <inheritdoc />
public TimeSpan Period { get => TimeSpan.FromHours(1); }

// No-op event as the period never changes on this job
public event EventHandler PeriodChanged { add { } remove { } }

private readonly ILogger<ContentVersionCleanupJob> _logger;
private readonly IContentVersionService _service;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,11 @@ namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
/// <summary>
/// Hosted service implementation for recurring health check notifications.
/// </summary>
public class HealthCheckNotifierJob : IRecurringBackgroundJob
public class HealthCheckNotifierJob : IDistributedBackgroundJob
{
/// <inheritdoc />
public string Name => "HealthCheckNotifierJob";
public TimeSpan Period { get; private set; }
public TimeSpan Delay { get; private set; }

private event EventHandler? _periodChanged;
public event EventHandler PeriodChanged
{
add { _periodChanged += value; }
remove { _periodChanged -= value; }
}

private readonly HealthCheckCollection _healthChecks;
private readonly ILogger<HealthCheckNotifierJob> _logger;
Expand Down Expand Up @@ -67,14 +61,12 @@ public HealthCheckNotifierJob(
_eventAggregator = eventAggregator;

Period = healthChecksSettings.CurrentValue.Notification.Period;
Delay = DelayCalculator.GetDelay(healthChecksSettings.CurrentValue.Notification.FirstRunTime, cronTabParser, logger, TimeSpan.FromMinutes(3));


healthChecksSettings.OnChange(x =>
{
_healthChecksSettings = x;
Period = x.Notification.Period;
_periodChanged?.Invoke(this, EventArgs.Empty);
});
}

Expand Down
11 changes: 4 additions & 7 deletions src/Umbraco.Infrastructure/BackgroundJobs/Jobs/LogScrubberJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Runtime;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Sync;

namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;

Expand All @@ -19,18 +16,18 @@ namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
/// <remarks>
/// Will only run on non-replica servers.
/// </remarks>
public class LogScrubberJob : IRecurringBackgroundJob
public class LogScrubberJob : IDistributedBackgroundJob
{
private readonly IAuditService _auditService;
private readonly ILogger<LogScrubberJob> _logger;
private readonly IProfilingLogger _profilingLogger;
private readonly ICoreScopeProvider _scopeProvider;
private LoggingSettings _settings;

public TimeSpan Period => TimeSpan.FromHours(4);
/// <inheritdoc />
public string Name => "LogScrubberJob";

// No-op event as the period never changes on this job
public event EventHandler PeriodChanged { add { } remove { } }
public TimeSpan Period => TimeSpan.FromHours(4);

/// <summary>
/// Initializes a new instance of the <see cref="LogScrubberJob" /> class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
/// <summary>
/// Cleans up long-running operations that have exceeded a specified age.
/// </summary>
public class LongRunningOperationsCleanupJob : IRecurringBackgroundJob
public class LongRunningOperationsCleanupJob : IDistributedBackgroundJob
{
private readonly ICoreScopeProvider _scopeProvider;
private readonly ILongRunningOperationRepository _longRunningOperationRepository;
Expand Down Expand Up @@ -36,18 +36,11 @@ public LongRunningOperationsCleanupJob(
}

/// <inheritdoc />
public event EventHandler? PeriodChanged
{
add { }
remove { }
}
public string Name => "LongRunningOperationsCleanupJob";

/// <inheritdoc />
public TimeSpan Period { get; }

/// <inheritdoc/>
public TimeSpan Delay { get; } = TimeSpan.FromSeconds(10);

/// <inheritdoc />
public async Task RunJobAsync()
{
Expand Down
Loading
Loading