Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding misfires with Quartz

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;
}
like image 710
Barium Scoorge Avatar asked Aug 18 '15 14:08

Barium Scoorge


People also ask

What is misfire threshold in quartz?

A misfire occurs if a persistent trigger “misses” its firing time because of the scheduler being shutdown, or because there are no available threads in Quartz's thread pool for executing the job. The different trigger types have different misfire instructions available to them.

What is trigger in quartz?

Trigger - a component that defines the schedule upon which a given Job will be executed. JobBuilder - used to define/build JobDetail instances, which define instances of Jobs.


1 Answers

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.

like image 90
Matt Thomas Avatar answered Oct 24 '22 13:10

Matt Thomas