New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactoring background jobs&worker integration #3382
Refactoring background jobs&worker integration #3382
Conversation
|
....BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpBackgroundJobsHangfireModule.cs
Outdated
Show resolved
Hide resolved
...lo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/AbpBackgroundJobsQuartzModule.cs
Outdated
Show resolved
Hide resolved
|
....Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
Show resolved
Hide resolved
@olicooper Could you check if it has been resolved #3422 ? Thanks. |
@liangshiw In your tests (see #3427 (comment)) you used |
@olicooper |
....BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzBackgroundWorkerManager.cs
Outdated
Show resolved
Hide resolved
Sorry for this.. After more testing I have found an edge case.. Line 48 in ae72356
What would be great is a method similar to 'AddJob' called 'AddTrigger' where we can replace the trigger but keep the old fire times until all the misfires have been fired. Quartz fires events off when things happen in the system, and I wonder if we can utilise these to call 'reschedule' at the appropriate time? Also, thank you for adding 'virtual' to the methods, this has helped a lot with testing! :) This is my job trigger: Trigger = TriggerBuilder.Create()
.WithIdentity($"{nameof(TenantStatisticsGenerationJob)}")
// If the schedule is missed, fire ALL missed jobs up to the current time
.WithCronSchedule("0 0 4 * * ?", opt => opt.WithMisfireHandlingInstructionIgnoreMisfires())
.StartNow().Build(); UPDATE: To clarify the issue... If we add this - Job misfires are handled properly, but changes made to the await _scheduler.AddJob(quartzWork.JobDetail, true);
await _scheduler.ResumeJob(quartzWork.JobDetail.Key); If we add this - Job misfires are not handled properly (only 1 misfire handled) but changes made to the await _scheduler.AddJob(quartzWork.JobDetail, true);
await _scheduler.ResumeJob(quartzWork.JobDetail.Key);
await _scheduler.RescheduleJob(quartzWork.Trigger.Key, quartzWork.Trigger); If we add this - Job misfires are not handled at all, but changes made to the await _scheduler.AddJob(quartzWork.JobDetail, true);
await _scheduler.RescheduleJob(quartzWork.Trigger.Key, quartzWork.Trigger); |
Hi @olicooper |
Yeah I figured this wouldn't be simple to fix. I will see if I can find anything that might help. Thank you for your work so far liangshiw. @hikalkan The current version of this code improves the existing implementation and fixes some bugs so it is probably worth merging in for the next version and maybe we can open a ticket to resolve the remaining issues? |
This comment has been minimized.
This comment has been minimized.
@olicooper |
@liangshiw I have tried to simplify and improve the code I wrote above. The problem I am trying to solve is the issue with misfire instructions not being properly handled (i.e. This time I check if any of the schedule related properties have changed for the I have tested changing the type of trigger schedule, updating various Trigger details, causing misfires by leaving application not running for a while etc. and all scenarios seem to work as expected. More testing will be required before releasing it though. @maliming Does this seem like a feasible solution to fix the bug? You can refactor it how you like but this is the basics: protected virtual async Task DefaultScheduleJobAsync(IQuartzBackgroundWorker quartzWork)
{
if (await _scheduler.CheckExists(quartzWork.JobDetail.Key))
{
await _scheduler.AddJob(quartzWork.JobDetail, true, true);
var originalTrigger = await _scheduler.GetTrigger(quartzWork.Trigger.Key);
if (ShouldPreserveTriggerFireState(originalTrigger, quartzWork.Trigger))
{
quartzWork.Trigger = CopyTriggerFireState(originalTrigger, quartzWork.Trigger);
}
await _scheduler.RescheduleJob(quartzWork.Trigger.Key, quartzWork.Trigger);
}
else
{
await _scheduler.ScheduleJob(quartzWork.JobDetail, quartzWork.Trigger);
}
}
protected bool ShouldPreserveTriggerFireState(ITrigger oldTrigger, ITrigger newTrigger)
{
// bypass object casting if the type doesn't match anyway
if (oldTrigger.GetType() != newTrigger.GetType()) return false;
//if (oldTrigger.HasMillisecondPrecision != newTrigger.HasMillisecondPrecision) return false;
switch (oldTrigger)
{
// CRON TRIGGER
case ICronTrigger oldCronTrigger when newTrigger is ICronTrigger newCronTrigger:
if (oldCronTrigger.CronExpressionString != newCronTrigger.CronExpressionString) return false;
if (!oldCronTrigger.TimeZone.Equals(newCronTrigger.TimeZone)) return false;
break;
// SIMPLE TRIGGER
case ISimpleTrigger oldSimplTrigger when newTrigger is ISimpleTrigger newSimplTrigger:
if (oldSimplTrigger.RepeatCount != newSimplTrigger.RepeatCount) return false;
if (oldSimplTrigger.RepeatInterval != newSimplTrigger.RepeatInterval) return false;
break;
// DAILY INTERVAL TRIGGER
case IDailyTimeIntervalTrigger oldDailyTrigger when newTrigger is IDailyTimeIntervalTrigger newDailyTrigger:
if (oldDailyTrigger.RepeatCount != newDailyTrigger.RepeatCount) return false;
if (oldDailyTrigger.RepeatIntervalUnit != newDailyTrigger.RepeatIntervalUnit) return false;
if (oldDailyTrigger.RepeatInterval != newDailyTrigger.RepeatInterval) return false;
if (((oldDailyTrigger.StartTimeOfDay == null) != (newDailyTrigger.StartTimeOfDay == null)) || !oldDailyTrigger.StartTimeOfDay.Equals(newDailyTrigger.StartTimeOfDay)) return false;
if (((oldDailyTrigger.EndTimeOfDay == null) != (newDailyTrigger.EndTimeOfDay == null)) || !oldDailyTrigger.EndTimeOfDay.Equals(newDailyTrigger.EndTimeOfDay)) return false;
if (((oldDailyTrigger.DaysOfWeek == null) != (newDailyTrigger.DaysOfWeek == null)) || oldDailyTrigger.DaysOfWeek.Count != newDailyTrigger.DaysOfWeek.Count) return false;
if (!oldDailyTrigger.TimeZone.Equals(newDailyTrigger.TimeZone)) return false;
break;
// CALENDAR INTERVAL TRIGGER
case ICalendarIntervalTrigger oldCalendarTrigger when newTrigger is ICalendarIntervalTrigger newCalendarTrigger:
if (oldCalendarTrigger.RepeatIntervalUnit != newCalendarTrigger.RepeatIntervalUnit) return false;
if (oldCalendarTrigger.RepeatInterval != newCalendarTrigger.RepeatInterval) return false;
if (!oldCalendarTrigger.TimeZone.Equals(newCalendarTrigger.TimeZone)) return false;
if (oldCalendarTrigger.PreserveHourOfDayAcrossDaylightSavings != newCalendarTrigger.PreserveHourOfDayAcrossDaylightSavings) return false;
if (oldCalendarTrigger.SkipDayIfHourDoesNotExist != newCalendarTrigger.SkipDayIfHourDoesNotExist) return false;
break;
default:
return false;
}
return true;
}
protected ITrigger CopyTriggerFireState(ITrigger fromTrigger, ITrigger toTrigger)
{
var triggertoUpdate = toTrigger;
if (triggertoUpdate is AbstractTrigger abstractTrigger)
{
abstractTrigger.SetPreviousFireTimeUtc(fromTrigger.GetPreviousFireTimeUtc());
abstractTrigger.SetNextFireTimeUtc(fromTrigger.GetNextFireTimeUtc());
abstractTrigger.StartTimeUtc = fromTrigger.StartTimeUtc;
}
switch (fromTrigger)
{
// CRON TRIGGER
//case ICronTrigger fromCronTrigger when triggertoUpdate is ICronTrigger toCronTrigger:
// break;
// SIMPLE TRIGGER
case ISimpleTrigger fromSimplTrigger when triggertoUpdate is ISimpleTrigger toSimplTrigger:
toSimplTrigger.TimesTriggered = fromSimplTrigger.TimesTriggered;
break;
// DAILY INTERVAL TRIGGER
case IDailyTimeIntervalTrigger fromDailyTrigger when triggertoUpdate is IDailyTimeIntervalTrigger toDailyTrigger:
toDailyTrigger.TimesTriggered = fromDailyTrigger.TimesTriggered;
break;
// CALENDAR INTERVAL TRIGGER
case ICalendarIntervalTrigger fromCalendarTrigger when triggertoUpdate is ICalendarIntervalTrigger toCalendarTrigger:
toCalendarTrigger.TimesTriggered = fromCalendarTrigger.TimesTriggered;
break;
}
return triggertoUpdate;
} |
.../Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzBackgroundJobManager.cs
Show resolved
Hide resolved
@olicooper |
Hi @liangshiw would it be possible to create a separate PR to make the methods virtual in the QuartzBackgroundWorkerManager so that it can be merged for the next (v2.7) release? @hikalkan moved this PR to v2.8 which isn't going to be released until 21st May. |
Hi @olicooper, sorry for the late reply. |
@liangshiw I suppose this work has been finalized, so I will review and merge? |
@hikalkan Yes, it's readly, please review. |
If you disable job execution, it will be null!
…was disabled but still trying to execute.
Thank you for the implementation. I tested with hangfire and rabbitmq, it is working as expected:
For Quartz, jobs are lost. Probably because I haven't used a persistence system for quartz, I don't have much experience on it. BTW, I fixed a bug: 783c162 |
Resolved: #3365.
Resolved: #3422