Skip to content

Commit 18381bf

Browse files
Refactor background jobs to use RecurringHostedServiceBase
Updates AiAuditLogCleanupBackgroundJob, AiUsageHourlyAggregationJob, AiUsageDailyRollupJob, and AiUsageStatisticsCleanupJob to inherit from RecurringHostedServiceBase instead of BackgroundService. Adds proper runtime state, server role, and MainDom checks to ensure jobs only run on appropriate servers in multi-instance scenarios. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 1599b62 commit 18381bf

8 files changed

Lines changed: 265 additions & 190 deletions

File tree

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<!-- Umbraco CMS -->
1616
<ItemGroup>
1717
<PackageVersion Include="Umbraco.Cms.Core" Version="[17.0.0, 17.999.999)" />
18+
<PackageVersion Include="Umbraco.Cms.Infrastructure" Version="[17.0.0, 17.999.999)" />
1819
<PackageVersion Include="Umbraco.Cms.Web.Common" Version="[17.0.0, 17.999.999)" />
1920
<PackageVersion Include="Umbraco.Cms.Api.Management" Version="[17.0.0, 17.999.999)" />
2021
<PackageVersion Include="Umbraco.Cms.Persistence.EFCore" Version="[17.0.0, 17.999.999)" />

Umbraco.Ai.Anthropic/src/Umbraco.Ai.Anthropic/packages.lock.json

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -942,39 +942,6 @@
942942
"Umbraco.Cms.Infrastructure": "[17.0.0, 18.0.0)"
943943
}
944944
},
945-
"Umbraco.Cms.Infrastructure": {
946-
"type": "Transitive",
947-
"resolved": "17.0.0",
948-
"contentHash": "35L5XwCAqu/xQIkupt4jspqY302FkGC/eKTJwAa67T+uH65bRdQ9DKC4LJN3wQOK93zaWYGglhbpqFYXKVtxcw==",
949-
"dependencies": {
950-
"Examine.Core": "3.7.1",
951-
"HtmlAgilityPack": "1.12.4",
952-
"MailKit": "4.14.0",
953-
"Markdown": "2.2.1",
954-
"Microsoft.Extensions.Configuration.Abstractions": "10.0.0",
955-
"Microsoft.Extensions.Configuration.Json": "10.0.0",
956-
"Microsoft.Extensions.DependencyInjection": "10.0.0",
957-
"Microsoft.Extensions.Http": "10.0.0",
958-
"Microsoft.Extensions.Identity.Stores": "10.0.0",
959-
"MiniProfiler.Shared": "4.5.4",
960-
"NPoco": "6.1.0",
961-
"OpenIddict.Abstractions": "7.1.0",
962-
"Serilog": "4.3.0",
963-
"Serilog.Enrichers.Process": "3.0.0",
964-
"Serilog.Enrichers.Thread": "4.0.0",
965-
"Serilog.Expressions": "5.0.0",
966-
"Serilog.Extensions.Hosting": "9.0.0",
967-
"Serilog.Formatting.Compact": "3.0.0",
968-
"Serilog.Formatting.Compact.Reader": "4.0.0",
969-
"Serilog.Settings.Configuration": "9.0.0",
970-
"Serilog.Sinks.Async": "2.1.0",
971-
"Serilog.Sinks.File": "7.0.0",
972-
"Serilog.Sinks.Map": "2.0.0",
973-
"System.Linq.Async": "7.0.0",
974-
"Umbraco.Cms.Core": "[17.0.0, 18.0.0)",
975-
"ncrontab": "3.4.0"
976-
}
977-
},
978945
"Umbraco.Cms.PublishedCache.HybridCache": {
979946
"type": "Transitive",
980947
"resolved": "17.0.0",
@@ -994,6 +961,7 @@
994961
"Microsoft.Extensions.Caching.Memory": "[10.0.0, 10.999.999)",
995962
"Microsoft.Extensions.Options": "[10.0.0, 10.999.999)",
996963
"Umbraco.Cms.Core": "[17.0.0, 17.999.999)",
964+
"Umbraco.Cms.Infrastructure": "[17.0.0, 17.999.999)",
997965
"Umbraco.Cms.Web.Common": "[17.0.0, 17.999.999)"
998966
}
999967
},
@@ -1058,6 +1026,40 @@
10581026
"Microsoft.Extensions.Options.DataAnnotations": "10.0.0"
10591027
}
10601028
},
1029+
"Umbraco.Cms.Infrastructure": {
1030+
"type": "CentralTransitive",
1031+
"requested": "[17.0.0, 17.999.999)",
1032+
"resolved": "17.0.0",
1033+
"contentHash": "35L5XwCAqu/xQIkupt4jspqY302FkGC/eKTJwAa67T+uH65bRdQ9DKC4LJN3wQOK93zaWYGglhbpqFYXKVtxcw==",
1034+
"dependencies": {
1035+
"Examine.Core": "3.7.1",
1036+
"HtmlAgilityPack": "1.12.4",
1037+
"MailKit": "4.14.0",
1038+
"Markdown": "2.2.1",
1039+
"Microsoft.Extensions.Configuration.Abstractions": "10.0.0",
1040+
"Microsoft.Extensions.Configuration.Json": "10.0.0",
1041+
"Microsoft.Extensions.DependencyInjection": "10.0.0",
1042+
"Microsoft.Extensions.Http": "10.0.0",
1043+
"Microsoft.Extensions.Identity.Stores": "10.0.0",
1044+
"MiniProfiler.Shared": "4.5.4",
1045+
"NPoco": "6.1.0",
1046+
"OpenIddict.Abstractions": "7.1.0",
1047+
"Serilog": "4.3.0",
1048+
"Serilog.Enrichers.Process": "3.0.0",
1049+
"Serilog.Enrichers.Thread": "4.0.0",
1050+
"Serilog.Expressions": "5.0.0",
1051+
"Serilog.Extensions.Hosting": "9.0.0",
1052+
"Serilog.Formatting.Compact": "3.0.0",
1053+
"Serilog.Formatting.Compact.Reader": "4.0.0",
1054+
"Serilog.Settings.Configuration": "9.0.0",
1055+
"Serilog.Sinks.Async": "2.1.0",
1056+
"Serilog.Sinks.File": "7.0.0",
1057+
"Serilog.Sinks.Map": "2.0.0",
1058+
"System.Linq.Async": "7.0.0",
1059+
"Umbraco.Cms.Core": "[17.0.0, 18.0.0)",
1060+
"ncrontab": "3.4.0"
1061+
}
1062+
},
10611063
"Umbraco.Cms.Web.Common": {
10621064
"type": "CentralTransitive",
10631065
"requested": "[17.0.0, 17.999.999)",

Umbraco.Ai.OpenAi/src/Umbraco.Ai.OpenAi/packages.lock.json

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -966,39 +966,6 @@
966966
"Umbraco.Cms.Infrastructure": "[17.0.0, 18.0.0)"
967967
}
968968
},
969-
"Umbraco.Cms.Infrastructure": {
970-
"type": "Transitive",
971-
"resolved": "17.0.0",
972-
"contentHash": "35L5XwCAqu/xQIkupt4jspqY302FkGC/eKTJwAa67T+uH65bRdQ9DKC4LJN3wQOK93zaWYGglhbpqFYXKVtxcw==",
973-
"dependencies": {
974-
"Examine.Core": "3.7.1",
975-
"HtmlAgilityPack": "1.12.4",
976-
"MailKit": "4.14.0",
977-
"Markdown": "2.2.1",
978-
"Microsoft.Extensions.Configuration.Abstractions": "10.0.0",
979-
"Microsoft.Extensions.Configuration.Json": "10.0.0",
980-
"Microsoft.Extensions.DependencyInjection": "10.0.0",
981-
"Microsoft.Extensions.Http": "10.0.0",
982-
"Microsoft.Extensions.Identity.Stores": "10.0.0",
983-
"MiniProfiler.Shared": "4.5.4",
984-
"NPoco": "6.1.0",
985-
"OpenIddict.Abstractions": "7.1.0",
986-
"Serilog": "4.3.0",
987-
"Serilog.Enrichers.Process": "3.0.0",
988-
"Serilog.Enrichers.Thread": "4.0.0",
989-
"Serilog.Expressions": "5.0.0",
990-
"Serilog.Extensions.Hosting": "9.0.0",
991-
"Serilog.Formatting.Compact": "3.0.0",
992-
"Serilog.Formatting.Compact.Reader": "4.0.0",
993-
"Serilog.Settings.Configuration": "9.0.0",
994-
"Serilog.Sinks.Async": "2.1.0",
995-
"Serilog.Sinks.File": "7.0.0",
996-
"Serilog.Sinks.Map": "2.0.0",
997-
"System.Linq.Async": "7.0.0",
998-
"Umbraco.Cms.Core": "[17.0.0, 18.0.0)",
999-
"ncrontab": "3.4.0"
1000-
}
1001-
},
1002969
"Umbraco.Cms.PublishedCache.HybridCache": {
1003970
"type": "Transitive",
1004971
"resolved": "17.0.0",
@@ -1018,6 +985,7 @@
1018985
"Microsoft.Extensions.Caching.Memory": "[10.0.0, 10.999.999)",
1019986
"Microsoft.Extensions.Options": "[10.0.0, 10.999.999)",
1020987
"Umbraco.Cms.Core": "[17.0.0, 17.999.999)",
988+
"Umbraco.Cms.Infrastructure": "[17.0.0, 17.999.999)",
1021989
"Umbraco.Cms.Web.Common": "[17.0.0, 17.999.999)"
1022990
}
1023991
},
@@ -1082,6 +1050,40 @@
10821050
"Microsoft.Extensions.Options.DataAnnotations": "10.0.0"
10831051
}
10841052
},
1053+
"Umbraco.Cms.Infrastructure": {
1054+
"type": "CentralTransitive",
1055+
"requested": "[17.0.0, 17.999.999)",
1056+
"resolved": "17.0.0",
1057+
"contentHash": "35L5XwCAqu/xQIkupt4jspqY302FkGC/eKTJwAa67T+uH65bRdQ9DKC4LJN3wQOK93zaWYGglhbpqFYXKVtxcw==",
1058+
"dependencies": {
1059+
"Examine.Core": "3.7.1",
1060+
"HtmlAgilityPack": "1.12.4",
1061+
"MailKit": "4.14.0",
1062+
"Markdown": "2.2.1",
1063+
"Microsoft.Extensions.Configuration.Abstractions": "10.0.0",
1064+
"Microsoft.Extensions.Configuration.Json": "10.0.0",
1065+
"Microsoft.Extensions.DependencyInjection": "10.0.0",
1066+
"Microsoft.Extensions.Http": "10.0.0",
1067+
"Microsoft.Extensions.Identity.Stores": "10.0.0",
1068+
"MiniProfiler.Shared": "4.5.4",
1069+
"NPoco": "6.1.0",
1070+
"OpenIddict.Abstractions": "7.1.0",
1071+
"Serilog": "4.3.0",
1072+
"Serilog.Enrichers.Process": "3.0.0",
1073+
"Serilog.Enrichers.Thread": "4.0.0",
1074+
"Serilog.Expressions": "5.0.0",
1075+
"Serilog.Extensions.Hosting": "9.0.0",
1076+
"Serilog.Formatting.Compact": "3.0.0",
1077+
"Serilog.Formatting.Compact.Reader": "4.0.0",
1078+
"Serilog.Settings.Configuration": "9.0.0",
1079+
"Serilog.Sinks.Async": "2.1.0",
1080+
"Serilog.Sinks.File": "7.0.0",
1081+
"Serilog.Sinks.Map": "2.0.0",
1082+
"System.Linq.Async": "7.0.0",
1083+
"Umbraco.Cms.Core": "[17.0.0, 18.0.0)",
1084+
"ncrontab": "3.4.0"
1085+
}
1086+
},
10851087
"Umbraco.Cms.Web.Common": {
10861088
"type": "CentralTransitive",
10871089
"requested": "[17.0.0, 17.999.999)",

Umbraco.Ai/src/Umbraco.Ai.Core/Analytics/AiUsageDailyRollupJob.cs

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,88 @@
1-
using Microsoft.Extensions.Hosting;
21
using Microsoft.Extensions.Logging;
32
using Microsoft.Extensions.Options;
3+
using Umbraco.Cms.Core;
4+
using Umbraco.Cms.Core.Runtime;
5+
using Umbraco.Cms.Core.Services;
6+
using Umbraco.Cms.Core.Sync;
7+
using Umbraco.Cms.Infrastructure.HostedServices;
48

59
namespace Umbraco.Ai.Core.Analytics;
610

711
/// <summary>
812
/// Background service that periodically rolls up hourly statistics into daily statistics.
913
/// Runs daily, processing completed days and catching up on any missed periods.
1014
/// </summary>
11-
internal sealed class AiUsageDailyRollupJob : BackgroundService
15+
internal sealed class AiUsageDailyRollupJob : RecurringHostedServiceBase
1216
{
1317
private readonly IAiUsageAggregationService _aggregationService;
1418
private readonly IAiUsageStatisticsRepository _statisticsRepository;
1519
private readonly IOptionsMonitor<AiAnalyticsOptions> _options;
20+
private readonly IRuntimeState _runtimeState;
21+
private readonly IServerRoleAccessor _serverRoleAccessor;
22+
private readonly IMainDom _mainDom;
1623
private readonly ILogger<AiUsageDailyRollupJob> _logger;
1724

18-
// Run every 6 hours (will process if needed)
19-
private static readonly TimeSpan CheckInterval = TimeSpan.FromHours(6);
25+
// Run every hour (will process if needed)
26+
private static readonly TimeSpan CheckInterval = TimeSpan.FromHours(1);
27+
private static readonly TimeSpan StartupDelay = TimeSpan.FromMinutes(1);
2028

2129
public AiUsageDailyRollupJob(
2230
IAiUsageAggregationService aggregationService,
2331
IAiUsageStatisticsRepository statisticsRepository,
2432
IOptionsMonitor<AiAnalyticsOptions> options,
33+
IRuntimeState runtimeState,
34+
IServerRoleAccessor serverRoleAccessor,
35+
IMainDom mainDom,
2536
ILogger<AiUsageDailyRollupJob> logger)
37+
: base(logger, CheckInterval, StartupDelay)
2638
{
2739
_aggregationService = aggregationService;
2840
_statisticsRepository = statisticsRepository;
2941
_options = options;
42+
_runtimeState = runtimeState;
43+
_serverRoleAccessor = serverRoleAccessor;
44+
_mainDom = mainDom;
3045
_logger = logger;
3146
}
3247

33-
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
48+
public override async Task PerformExecuteAsync(object? state)
3449
{
35-
_logger.LogInformation("AI Usage Daily Rollup Job started");
50+
// Don't run if analytics is disabled
51+
if (!_options.CurrentValue.Enabled)
52+
{
53+
_logger.LogDebug("Analytics disabled, skipping daily rollup");
54+
return;
55+
}
3656

37-
// Wait a bit on startup to let other services initialize
38-
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
57+
// Don't run unless Umbraco is running
58+
if (_runtimeState.Level != RuntimeLevel.Run)
59+
{
60+
return;
61+
}
3962

40-
while (!stoppingToken.IsCancellationRequested)
63+
// Don't run on replicas nor unknown role servers
64+
switch (_serverRoleAccessor.CurrentServerRole)
4165
{
42-
try
43-
{
44-
if (!_options.CurrentValue.Enabled)
45-
{
46-
_logger.LogDebug("Analytics disabled, skipping daily rollup");
47-
await Task.Delay(CheckInterval, stoppingToken);
48-
continue;
49-
}
50-
51-
await ProcessMissingDaysAsync(stoppingToken);
52-
}
53-
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
54-
{
55-
// Expected when shutting down
66+
case ServerRole.Subscriber:
67+
_logger.LogDebug("AI Usage Daily Rollup will not run on subscriber servers.");
68+
return;
69+
case ServerRole.Unknown:
70+
_logger.LogDebug("AI Usage Daily Rollup will not run on servers with unknown role.");
71+
return;
72+
case ServerRole.Single:
73+
case ServerRole.SchedulingPublisher:
74+
default:
5675
break;
57-
}
58-
catch (Exception ex)
59-
{
60-
_logger.LogError(ex, "Error in daily rollup job, will retry on next run");
61-
}
76+
}
6277

63-
// Wait before next check
64-
await Task.Delay(CheckInterval, stoppingToken);
78+
// Ensure we do not run if not main domain
79+
if (!_mainDom.IsMainDom)
80+
{
81+
_logger.LogDebug("AI Usage Daily Rollup will not run if not MainDom.");
82+
return;
6583
}
6684

67-
_logger.LogInformation("AI Usage Daily Rollup Job stopped");
85+
await ProcessMissingDaysAsync(CancellationToken.None);
6886
}
6987

7088
/// <summary>

0 commit comments

Comments
 (0)