Browse Source

Enhance dynamic background worker management with improved error handling and logging

pull/25059/head
maliming 6 months ago
parent
commit
eb2cf10a3d
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 2
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobExecutorJob.cs
  2. 2
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerRegistry.cs
  3. 11
      framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerManager.cs
  4. 9
      framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerAdapter.cs
  5. 30
      framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerManager.cs
  6. 7
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/IDynamicBackgroundWorkerManager.cs

2
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobExecutorJob.cs

@ -22,7 +22,7 @@ public class DynamicBackgroundJobExecutorJob : AsyncBackgroundJob<DynamicBackgro
public override async Task ExecuteAsync(DynamicBackgroundJobArgs args) public override async Task ExecuteAsync(DynamicBackgroundJobArgs args)
{ {
Logger.LogInformation( Logger.LogDebug(
"Executing dynamic job. TransportJobName: {TransportJobName}, EffectiveJobName: {EffectiveJobName}", "Executing dynamic job. TransportJobName: {TransportJobName}, EffectiveJobName: {EffectiveJobName}",
DynamicBackgroundJobArgs.JobNameConstant, DynamicBackgroundJobArgs.JobNameConstant,
args.JobName args.JobName

2
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerRegistry.cs

@ -42,7 +42,7 @@ public class DynamicBackgroundJobHandlerRegistry : IDynamicBackgroundJobHandlerR
public virtual IReadOnlyCollection<string> GetAllNames() public virtual IReadOnlyCollection<string> GetAllNames()
{ {
return Handlers.Keys.ToList(); return Handlers.Keys.ToList().AsReadOnly();
} }
public virtual void Clear() public virtual void Clear()

11
framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerManager.cs

@ -47,8 +47,17 @@ public class HangfireDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMa
cronExpression = GetCron(period); cronExpression = GetCron(period);
} }
ScheduleRecurringJob(workerName, cronExpression, cancellationToken); // Register the handler first so it is available the moment the recurring job fires.
HandlerRegistry.Register(workerName, handler); HandlerRegistry.Register(workerName, handler);
try
{
ScheduleRecurringJob(workerName, cronExpression, cancellationToken);
}
catch
{
HandlerRegistry.Unregister(workerName);
throw;
}
return Task.CompletedTask; return Task.CompletedTask;
} }

9
framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerAdapter.cs

@ -26,13 +26,14 @@ public class QuartzDynamicBackgroundWorkerAdapter : IJob, ITransientDependency
public virtual async Task Execute(IJobExecutionContext context) public virtual async Task Execute(IJobExecutionContext context)
{ {
var workerName = context.MergedJobDataMap.GetString(QuartzDynamicBackgroundWorkerManager.DynamicWorkerNameKey); var rawWorkerName = context.MergedJobDataMap.GetString(QuartzDynamicBackgroundWorkerManager.DynamicWorkerNameKey);
if (string.IsNullOrWhiteSpace(workerName)) if (string.IsNullOrWhiteSpace(rawWorkerName))
{ {
return; return;
} }
var handler = HandlerRegistry.Get(workerName!); var workerName = rawWorkerName!;
var handler = HandlerRegistry.Get(workerName);
if (handler == null) if (handler == null)
{ {
Logger.LogWarning("No handler registered for dynamic worker: {WorkerName}", workerName); Logger.LogWarning("No handler registered for dynamic worker: {WorkerName}", workerName);
@ -42,7 +43,7 @@ public class QuartzDynamicBackgroundWorkerAdapter : IJob, ITransientDependency
try try
{ {
await handler( await handler(
new DynamicBackgroundWorkerExecutionContext(workerName!, ServiceProvider), new DynamicBackgroundWorkerExecutionContext(workerName, ServiceProvider),
context.CancellationToken); context.CancellationToken);
} }
catch (Exception ex) catch (Exception ex)

30
framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerManager.cs

@ -48,16 +48,24 @@ public class QuartzDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMana
var trigger = BuildTrigger(schedule, jobDetail, triggerKey); var trigger = BuildTrigger(schedule, jobDetail, triggerKey);
// Use replace=true to avoid TOCTOU race between CheckExists and ScheduleJob. // Register the handler first so it is available the moment the job fires.
await Scheduler.ScheduleJobs(
new Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>>
{
{ jobDetail, new[] { trigger } }
},
replace: true,
cancellationToken);
HandlerRegistry.Register(workerName, handler); HandlerRegistry.Register(workerName, handler);
try
{
// Use replace=true to avoid TOCTOU race between CheckExists and ScheduleJob.
await Scheduler.ScheduleJobs(
new Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>>
{
{ jobDetail, new[] { trigger } }
},
replace: true,
cancellationToken);
}
catch
{
HandlerRegistry.Unregister(workerName);
throw;
}
} }
public virtual async Task<bool> RemoveAsync(string workerName, CancellationToken cancellationToken = default) public virtual async Task<bool> RemoveAsync(string workerName, CancellationToken cancellationToken = default)
@ -68,10 +76,10 @@ public class QuartzDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMana
// This ensures cleanup works correctly after an application restart, when the registry // This ensures cleanup works correctly after an application restart, when the registry
// is empty but the Quartz job may still exist in the scheduler store. // is empty but the Quartz job may still exist in the scheduler store.
var jobKey = new JobKey($"DynamicWorker:{workerName}"); var jobKey = new JobKey($"DynamicWorker:{workerName}");
await Scheduler.DeleteJob(jobKey, cancellationToken); var deleted = await Scheduler.DeleteJob(jobKey, cancellationToken);
var wasRegistered = HandlerRegistry.Unregister(workerName); var wasRegistered = HandlerRegistry.Unregister(workerName);
return wasRegistered; return deleted || wasRegistered;
} }
public virtual async Task<bool> UpdateScheduleAsync( public virtual async Task<bool> UpdateScheduleAsync(

7
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/IDynamicBackgroundWorkerManager.cs

@ -21,7 +21,12 @@ public interface IDynamicBackgroundWorkerManager
/// <summary> /// <summary>
/// Removes a previously added dynamic worker by name. /// Removes a previously added dynamic worker by name.
/// Returns true if the worker was found and removed; false otherwise. /// Always attempts to remove both the in-memory handler and any persistent scheduling record
/// (Hangfire RecurringJob or Quartz job), regardless of current in-memory state.
/// Returns true if the handler was registered in memory at the time of the call, or if
/// the persistent scheduling record was found and deleted (provider-dependent).
/// May return false after an application restart even if a persistent record was cleaned up,
/// when the provider cannot report whether a persistent record existed (e.g. Hangfire).
/// </summary> /// </summary>
Task<bool> RemoveAsync(string workerName, CancellationToken cancellationToken = default); Task<bool> RemoveAsync(string workerName, CancellationToken cancellationToken = default);

Loading…
Cancel
Save