Browse Source

Enhance dynamic background job handling with improved argument management and error logging

pull/25059/head
maliming 1 week ago
parent
commit
7a1e246ce9
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 11
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobArgs.cs
  2. 12
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerRegistry.cs
  3. 6
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerRegistry.cs
  4. 12
      framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs
  5. 5
      framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerAdapter.cs
  6. 24
      framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerManager.cs
  7. 5
      framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerAdapter.cs
  8. 31
      framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerManager.cs
  9. 10
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/DefaultDynamicBackgroundWorkerManager.cs

11
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobArgs.cs

@ -5,9 +5,16 @@ public class DynamicBackgroundJobArgs
{
public const string JobNameConstant = "Abp.DynamicJob";
public string JobName { get; }
public string JobName { get; private set; }
public string JsonData { get; }
public string JsonData { get; private set; }
// For serializers that require a parameterless constructor (e.g. System.Text.Json)
private DynamicBackgroundJobArgs()
{
JobName = string.Empty;
JsonData = string.Empty;
}
public DynamicBackgroundJobArgs(string jobName, string jsonData)
{

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

@ -1,4 +1,6 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BackgroundJobs;
@ -37,4 +39,14 @@ public class DynamicBackgroundJobHandlerRegistry : IDynamicBackgroundJobHandlerR
Check.NotNullOrWhiteSpace(jobName, nameof(jobName));
return Handlers.TryGetValue(jobName, out var handler) ? handler : null;
}
public virtual IReadOnlyCollection<string> GetAllNames()
{
return Handlers.Keys.ToList();
}
public virtual void Clear()
{
Handlers.Clear();
}
}

6
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerRegistry.cs

@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace Volo.Abp.BackgroundJobs;
public interface IDynamicBackgroundJobHandlerRegistry
@ -9,4 +11,8 @@ public interface IDynamicBackgroundJobHandlerRegistry
bool IsRegistered(string jobName);
DynamicBackgroundJobHandler? Get(string jobName);
IReadOnlyCollection<string> GetAllNames();
void Clear();
}

12
framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs

@ -162,19 +162,23 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet
if (time.TotalSeconds <= 59)
{
cron = $"*/{time.TotalSeconds} * * * * *";
var seconds = Math.Max(1, (int)Math.Round(time.TotalSeconds));
cron = $"*/{seconds} * * * * *";
}
else if (time.TotalMinutes <= 59)
{
cron = $"*/{time.TotalMinutes} * * * *";
var minutes = Math.Max(1, (int)Math.Round(time.TotalMinutes));
cron = $"*/{minutes} * * * *";
}
else if (time.TotalHours <= 23)
{
cron = $"0 */{time.TotalHours} * * *";
var hours = Math.Max(1, (int)Math.Round(time.TotalHours));
cron = $"0 */{hours} * * *";
}
else if(time.TotalDays <= 31)
{
cron = $"0 0 0 1/{time.TotalDays} * *";
var days = Math.Max(1, (int)Math.Round(time.TotalDays));
cron = $"0 0 0 1/{days} * *";
}
else
{

5
framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerAdapter.cs

@ -39,10 +39,13 @@ public class HangfireDynamicBackgroundWorkerAdapter : ITransientDependency
}
catch (Exception ex)
{
// Swallow the exception to match the behavior of AsyncPeriodicBackgroundWorkerBase,
// which catches, notifies and logs without rethrowing. This prevents Hangfire from
// treating a single failed execution as a job failure and triggering retries.
await ServiceProvider.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex));
throw;
Logger.LogException(ex);
}
}
}

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

@ -57,16 +57,14 @@ public class HangfireDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMa
{
Check.NotNullOrWhiteSpace(workerName, nameof(workerName));
if (!HandlerRegistry.IsRegistered(workerName))
{
return Task.FromResult(false);
}
// Always remove the persistent recurring job regardless of in-memory registry state.
// This ensures cleanup works correctly after an application restart, when the registry
// is empty but the Hangfire recurring job may still exist in the database.
var recurringJobId = $"DynamicWorker:{workerName}";
RecurringJob.RemoveIfExists(recurringJobId);
HandlerRegistry.Unregister(workerName);
var wasRegistered = HandlerRegistry.Unregister(workerName);
return Task.FromResult(true);
return Task.FromResult(wasRegistered);
}
public virtual Task<bool> UpdateScheduleAsync(
@ -150,19 +148,23 @@ public class HangfireDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMa
if (time.TotalSeconds <= 59)
{
cron = $"*/{time.TotalSeconds} * * * * *";
var seconds = Math.Max(1, (int)Math.Round(time.TotalSeconds));
cron = $"*/{seconds} * * * * *";
}
else if (time.TotalMinutes <= 59)
{
cron = $"*/{time.TotalMinutes} * * * *";
var minutes = Math.Max(1, (int)Math.Round(time.TotalMinutes));
cron = $"*/{minutes} * * * *";
}
else if (time.TotalHours <= 23)
{
cron = $"0 */{time.TotalHours} * * *";
var hours = Math.Max(1, (int)Math.Round(time.TotalHours));
cron = $"0 */{hours} * * *";
}
else if (time.TotalDays <= 31)
{
cron = $"0 0 0 1/{time.TotalDays} * *";
var days = Math.Max(1, (int)Math.Round(time.TotalDays));
cron = $"0 0 0 1/{days} * *";
}
else
{

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

@ -47,10 +47,13 @@ public class QuartzDynamicBackgroundWorkerAdapter : IJob, ITransientDependency
}
catch (Exception ex)
{
// Swallow the exception to match the behavior of AsyncPeriodicBackgroundWorkerBase,
// which catches, notifies and logs without rethrowing. This prevents Quartz from
// treating a single failed execution as a job failure and triggering retries.
await ServiceProvider.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex));
throw;
Logger.LogException(ex);
}
}
}

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

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@ -47,16 +48,14 @@ public class QuartzDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMana
var trigger = BuildTrigger(schedule, jobDetail, triggerKey);
if (await Scheduler.CheckExists(jobDetail.Key, cancellationToken))
{
await Scheduler.AddJob(jobDetail, true, true, cancellationToken);
await Scheduler.ResumeJob(jobDetail.Key, cancellationToken);
await Scheduler.RescheduleJob(trigger.Key, trigger, cancellationToken);
}
else
{
await Scheduler.ScheduleJob(jobDetail, trigger, cancellationToken);
}
// 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);
HandlerRegistry.Register(workerName, handler);
}
@ -65,16 +64,14 @@ public class QuartzDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMana
{
Check.NotNullOrWhiteSpace(workerName, nameof(workerName));
if (!HandlerRegistry.IsRegistered(workerName))
{
return false;
}
// Always delete the persistent Quartz job regardless of in-memory registry state.
// 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.
var jobKey = new JobKey($"DynamicWorker:{workerName}");
await Scheduler.DeleteJob(jobKey, cancellationToken);
var deleted = await Scheduler.DeleteJob(jobKey, cancellationToken);
HandlerRegistry.Unregister(workerName);
return true;
return deleted;
}
public virtual async Task<bool> UpdateScheduleAsync(

10
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/DefaultDynamicBackgroundWorkerManager.cs

@ -49,6 +49,11 @@ public class DefaultDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMan
await _semaphore.WaitAsync(cancellationToken);
try
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(DefaultDynamicBackgroundWorkerManager));
}
if (_dynamicWorkers.TryRemove(workerName, out var existingWorker))
{
await existingWorker.StopAsync(cancellationToken);
@ -107,6 +112,11 @@ public class DefaultDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMan
await _semaphore.WaitAsync(cancellationToken);
try
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(DefaultDynamicBackgroundWorkerManager));
}
if (!_dynamicWorkers.TryGetValue(workerName, out var worker))
{
return false;

Loading…
Cancel
Save