1- using Microsoft . Extensions . Logging ;
1+ using Microsoft . Extensions . DependencyInjection ;
2+ using Microsoft . Extensions . Logging ;
3+ using Microsoft . Extensions . Options ;
24using Umbraco . Cms . Core ;
5+ using Umbraco . Cms . Core . Configuration . Models ;
6+ using Umbraco . Cms . Core . DependencyInjection ;
37using Umbraco . Cms . Core . Scoping ;
48using Umbraco . Cms . Infrastructure . BackgroundJobs ;
59using Umbraco . Cms . Infrastructure . Models ;
@@ -14,24 +18,41 @@ public class DistributedJobService : IDistributedJobService
1418 private readonly IDistributedJobRepository _distributedJobRepository ;
1519 private readonly IEnumerable < IDistributedBackgroundJob > _distributedBackgroundJobs ;
1620 private readonly ILogger < DistributedJobService > _logger ;
21+ private readonly DistributedJobSettings _settings ;
1722
1823 /// <summary>
1924 /// Initializes a new instance of the <see cref="DistributedJobService"/> class.
2025 /// </summary>
21- /// <param name="coreScopeProvider"></param>
22- /// <param name="distributedJobRepository"></param>
23- /// <param name="distributedBackgroundJobs"></param>
24- /// <param name="logger"></param>
26+ [ Obsolete ( "Use the constructor that accepts IOptions<DistributedJobSettings>. Scheduled for removal in V18." ) ]
2527 public DistributedJobService (
2628 ICoreScopeProvider coreScopeProvider ,
2729 IDistributedJobRepository distributedJobRepository ,
2830 IEnumerable < IDistributedBackgroundJob > distributedBackgroundJobs ,
2931 ILogger < DistributedJobService > logger )
32+ : this (
33+ coreScopeProvider ,
34+ distributedJobRepository ,
35+ distributedBackgroundJobs ,
36+ logger ,
37+ StaticServiceProvider . Instance . GetRequiredService < IOptions < DistributedJobSettings > > ( ) )
38+ {
39+ }
40+
41+ /// <summary>
42+ /// Initializes a new instance of the <see cref="DistributedJobService"/> class.
43+ /// </summary>
44+ public DistributedJobService (
45+ ICoreScopeProvider coreScopeProvider ,
46+ IDistributedJobRepository distributedJobRepository ,
47+ IEnumerable < IDistributedBackgroundJob > distributedBackgroundJobs ,
48+ ILogger < DistributedJobService > logger ,
49+ IOptions < DistributedJobSettings > settings )
3050 {
3151 _coreScopeProvider = coreScopeProvider ;
3252 _distributedJobRepository = distributedJobRepository ;
3353 _distributedBackgroundJobs = distributedBackgroundJobs ;
3454 _logger = logger ;
55+ _settings = settings . Value ;
3556 }
3657
3758 /// <inheritdoc />
@@ -42,7 +63,8 @@ public DistributedJobService(
4263 scope . EagerWriteLock ( Constants . Locks . DistributedJobs ) ;
4364
4465 IEnumerable < DistributedBackgroundJobModel > jobs = _distributedJobRepository . GetAll ( ) ;
45- DistributedBackgroundJobModel ? job = jobs . FirstOrDefault ( x => x . LastRun < DateTime . UtcNow - x . Period ) ;
66+ DistributedBackgroundJobModel ? job = jobs . FirstOrDefault ( x => x . LastRun < DateTime . UtcNow - x . Period
67+ && ( x . IsRunning is false || x . LastAttemptedRun < DateTime . UtcNow - x . Period - _settings . MaximumExecutionTime ) ) ;
4668
4769 if ( job is null )
4870 {
@@ -93,45 +115,64 @@ public async Task FinishAsync(string jobName)
93115 /// <inheritdoc />
94116 public async Task EnsureJobsAsync ( )
95117 {
118+ // Pre-compute registered job data outside the lock to minimize lock hold time
119+ var registeredJobsByName = _distributedBackgroundJobs . ToDictionary ( x => x . Name , x => x . Period ) ;
120+
121+ // Early exit if no registered jobs
122+ if ( registeredJobsByName . Count is 0 )
123+ {
124+ return ;
125+ }
126+
96127 using ICoreScope scope = _coreScopeProvider . CreateCoreScope ( ) ;
97128 scope . WriteLock ( Constants . Locks . DistributedJobs ) ;
98129
99130 DistributedBackgroundJobModel [ ] existingJobs = _distributedJobRepository . GetAll ( ) . ToArray ( ) ;
100131 var existingJobsByName = existingJobs . ToDictionary ( x => x . Name ) ;
101132
102- foreach ( IDistributedBackgroundJob registeredJob in _distributedBackgroundJobs )
133+ // Collect all changes first, then execute - minimizes time spent in the critical section
134+ var jobsToAdd = new List < DistributedBackgroundJobModel > ( ) ;
135+ DateTime utcNow = DateTime . UtcNow ;
136+
137+ foreach ( KeyValuePair < string , TimeSpan > registeredJob in registeredJobsByName )
103138 {
104- if ( existingJobsByName . TryGetValue ( registeredJob . Name , out DistributedBackgroundJobModel ? existingJob ) )
139+ if ( existingJobsByName . TryGetValue ( registeredJob . Key , out DistributedBackgroundJobModel ? existingJob ) )
105140 {
106- // Update if period has changed
107- if ( existingJob . Period != registeredJob . Period )
141+ // Update only if period has actually changed
142+ if ( existingJob . Period != registeredJob . Value )
108143 {
109- existingJob . Period = registeredJob . Period ;
144+ existingJob . Period = registeredJob . Value ;
110145 _distributedJobRepository . Update ( existingJob ) ;
111146 }
112147 }
113148 else
114149 {
115- // Add new job (fresh install or newly registered job)
116- var newJob = new DistributedBackgroundJobModel
150+ // Collect new jobs for batch insert
151+ jobsToAdd . Add ( new DistributedBackgroundJobModel
117152 {
118- Name = registeredJob . Name ,
119- Period = registeredJob . Period ,
120- LastRun = DateTime . UtcNow ,
153+ Name = registeredJob . Key ,
154+ Period = registeredJob . Value ,
155+ LastRun = utcNow ,
121156 IsRunning = false ,
122- LastAttemptedRun = DateTime . UtcNow ,
123- } ;
124- _distributedJobRepository . Add ( newJob ) ;
157+ LastAttemptedRun = utcNow ,
158+ } ) ;
125159 }
126160 }
127161
128- // Remove jobs that are no longer registered in code
129- var registeredJobNames = _distributedBackgroundJobs . Select ( x => x . Name ) . ToHashSet ( ) ;
130- IEnumerable < DistributedBackgroundJobModel > jobsToRemove = existingJobs . Where ( x => registeredJobNames . Contains ( x . Name ) is false ) ;
162+ // Batch insert new jobs
163+ if ( jobsToAdd . Count > 0 )
164+ {
165+ _distributedJobRepository . Add ( jobsToAdd ) ;
166+ }
167+
168+ // Batch delete jobs that are no longer registered
169+ var jobsToRemove = existingJobs
170+ . Where ( x => registeredJobsByName . ContainsKey ( x . Name ) is false )
171+ . ToList ( ) ;
131172
132- foreach ( DistributedBackgroundJobModel jobToRemove in jobsToRemove )
173+ if ( jobsToRemove . Count > 0 )
133174 {
134- _distributedJobRepository . Delete ( jobToRemove ) ;
175+ _distributedJobRepository . Delete ( jobsToRemove ) ;
135176 }
136177
137178 scope . Complete ( ) ;
0 commit comments