I'm facing a painful problem with Quartz and misfires.
My App creates allows user to create CronTrigger and SimpleTrigger Jobs.
Each Job can be paused/resumed (using Scheduler.pauseJob and Scheduler.resumeJob)
The scheduler itself can be set to standby.
We'd like to discard any misfires :
- When scheduler is in standby
- When job is paused
- When the application is stopped
As explained in this blog post http://www.nurkiewicz.com/2012/04/quartz-scheduler-misfire-instructions.html, I've tried
- withMisfireHandlingInstructionDoNothing policy for CronTrigger
- withMisfireHandlingInstructionNextWithRemainingCount for SimpleTrigger
but none could discard misfires.
I'm currently using an ugly workaround in job execute method:
public void execute(JobExecutionContext context) throws JobExecutionException {
Date dateNow = new Date();
long diff = dateNow.getTime() - context.getScheduledFireTime().getTime();
if (diff > 500)
{
//its a misfire
return;
}
/* rest of job execution code */
When scheduledFireTime is more than 500ms older than now, discard it.
But it seems that sometimes, in production, this allows some jobs to be executed (it has been reported that occurs when app is restarted)
Could that be possible ? Is there any pretty way to avoid misfires ?
Quartz version : 2.1.7 (In a Spring 3.2.5 app)
quartz.properties
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.dataSource=psqldatasource
org.quartz.dataSource.psqldatasource.driver=${db.driver}
org.quartz.dataSource.psqldatasource.URL=${db.url}
org.quartz.dataSource.psqldatasource.user=${db.usr}
org.quartz.dataSource.psqldatasource.password=${db.pwd}
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
for Cron :
JobDetail job = JobBuilder.newJob(JobLauncher.class)
.withIdentity(jobIdentifierBean.getNameJob(), jobIdentifierBean.getNameGroup())
.usingJobData(...) //defining some jobData key/values
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(name, group)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(strCronExpression).withMisfireHandlingInstructionDoNothing())
.build();
try {
scheduler.scheduleJob(job, trigger);
} catch (SchedulerException e) {
throw e;
}
for SimpleTrigger (job is defined as in cron case)
//avoid first fire
GregorianCalendar g = new GregorianCalendar();
switch (enumDelayUnits) {
case Days:
g.add(GregorianCalendar.HOUR, delay * 24);
break;
case Hours:
g.add(GregorianCalendar.HOUR, delay);
break;
case Minutes:
g.add(GregorianCalendar.MINUTE, delay);
break;
case Seconds:
g.add(GregorianCalendar.SECOND, delay);
break;
default:
throw new ServiceException("Unknow delay type");
}
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobIdentifierBean.getNameTrigger(), jobIdentifierBean.getNameGroup())
.startAt(g.getTime())
.withSchedule(getSimpleScheduleBuilder(delay, enumDelayUnits, repeatForever))
.build();
try {
scheduler.scheduleJob(job, trigger);
} catch (SchedulerException e) {
throw e;
}
private SimpleScheduleBuilder getSimpleScheduleBuilder(int delay, EnumDelayUnits enumDelayType, boolean repeatForever) throws ServiceException, Exception
{
SimpleScheduleBuilder simpleScheduleBuilder;
simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
switch (enumDelayType) {
case Days:
simpleScheduleBuilder.withIntervalInHours(24 * delay);
break;
case Hours:
simpleScheduleBuilder.withIntervalInHours(delay);
break;
case Minutes:
simpleScheduleBuilder.withIntervalInMinutes(delay);
break;
case Seconds:
simpleScheduleBuilder.withIntervalInSeconds(delay);
break;
default:
serviceError("Unknown delay " + enumDelayType);
}
if(repeatForever)
{
simpleScheduleBuilder.repeatForever();
}
simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionNextWithRemainingCount();
return simpleScheduleBuilder;
}