Java – Avoiding misfires with Quartz

javaquartz-scheduler

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;
}

Best Answer

I am not sure if you ever figured this out, but I had a similar issue and the solution for me was related to the "misfireThreshold" in my configuration:

quartz.jobStore.misfireThreshold

Mine was set to 60000 (aka 1 minute) and so upon restarting my scheduling service, if they were within a minute of being "late", they still executed.