7
votes

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;
}
1
Can we see your quartz configuration? The "none could discard misfires" is suspicious, if it were true probably it would be a huge bug reported by many a long time ago.Gergely Bacso
edited with quartz propertiesBarium Scoorge
The recently added config is mainly the jobstore configuration. The relevant part is where you are defining your schedulers, jobs, triggers, etc.Gergely Bacso
edited with definitions..Barium Scoorge
Hi, ObscurMoirage have you found a solution to this issue? I'm facing the same problem and I have misfires when my web app restartslux83

1 Answers

7
votes

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.