Search code examples
javaquartz-schedulerquartz

Quartz retry job with exponentially increasing delay time strategy for trigger


I use quartz in spring project. The idea is to create separate job for new income data, that was not delivered successfully to target service.

  • To deliver data with exponentially increasing strategy I create job for next unsuccessful tries also. (It spams DB. It seems works, but can be better.)
  • different my solution is to create repeatable crone based job, that scans DB data and loads only date should be handled now because time passed (in this case I have to manage a lot of on java part).

Both ways can provide correct flow, but I prefer out the box solution.

I tried to manage JobExecutionContext of triggered job to use same JobDetail registering it in quartzScheduler. The idea was to update existing job with different trigger. But issue is quartz tries to create new job persisting it in DB.

org.quartz.ObjectAlreadyExistsException: Unable to store Job : 'digex-caas-securepay.b333e5bf-583f-4643-9ad7-ef4b913001f7', because one already exists with this identification.
    at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJob(JobStoreSupport.java:1113) ~[quartz-2.3.0.jar:na]
    at org.quartz.impl.jdbcjobstore.JobStoreSupport$2.executeVoid(JobStoreSupport.java:1067) ~[quartz-2.3.0.jar:na]
    at org.quartz.impl.jdbcjobstore.JobStoreSupport$VoidTransactionCallback.execute(JobStoreSupport.java:3765) ~[quartz-2.3.0.jar:na]
    at org.quartz.impl.jdbcjobstore.JobStoreSupport$VoidTransactionCallback.execute(JobStoreSupport.java:3763) ~[quartz-2.3.0.jar:na]
    at org.quartz.impl.jdbcjobstore.JobStoreCMT.executeInLock(JobStoreCMT.java:245) ~[quartz-2.3.0.jar:na]
    at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJobAndTrigger(JobStoreSupport.java:1063) ~[quartz-2.3.0.jar:na]
    at org.quartz.core.QuartzScheduler.scheduleJob(QuartzScheduler.java:855) ~[quartz-2.3.0.jar:na]
    at org.quartz.impl.StdScheduler.scheduleJob(StdScheduler.java:249) ~[quartz-2.3.0.jar:na]
    at com.incomm.ecomm.services.quartz.OrderQuartzJobScheduler.registerSecurePayPostServiceJob(OrderQuartzJobScheduler.java:59) ~[classes/:na]

Questions are (please answer any):

  • How to manage via quartz job trigger updates (in case it was not handled successfully - update trigger with different fired time)?
  • How to manage via quartz job updates (in case it was not handled successfully - update job with new trigger)?
  • How to register exponentially increasing delay time strategy for trigger?

Solution

  • Simple answer is:

    scheduler.rescheduleJob(trigger.getKey(), trigger);
    

    Detailed answer is:

    • How to manage via quartz job trigger updates

    scheduler.rescheduleJob(trigger.getKey(), trigger);

    • How to manage via quartz job updates

    It is not important any more in case trigger updated.

    • How to register exponentially increasing delay time strategy for trigger?

    single trigger can be rescheduled with any different time. Time for next execution can be calculated using any implementation of IntervalCalculationStrategy.

    Example of reschedule the job:

    The job and job details can be taken from JobExecutionContext but not necessary. Trigger can be connected to only one job, so it good enough for quartz specify triggerKey to be updated:

    @Autowired
    private Scheduler scheduler;
    @Autowired
    private IntervalCalculationStrategy intervalCalculation;
    
    public <T extends QuartzJobBean> void registerSecurePayPostServiceJob(
        JobExecutionContext firedJobExecutionContext) {
      Optional<SimpleTriggerImpl> mutableTrigger =
          ofNullable(firedJobExecutionContext)
              .map(JobExecutionContext::getTrigger)
              .filter(SimpleTriggerImpl.class::isInstance)
              .map(SimpleTriggerImpl.class::cast);
      try {
        if (mutableTrigger.isPresent()) {
          SimpleTriggerImpl trigger = mutableTrigger.get();
          int nextAttemptNumber = trigger.getTimesTriggered();
          log.trace("trigger: {} fired [{}] times", trigger.getFullName(),
              trigger.getTimesTriggered());
          trigger.setStartTime(intervalCalculation.calculateNextTryDate(nextAttemptNumber));
          this.scheduler.rescheduleJob(trigger.getKey(), trigger);
        }
      } catch (SchedulerException e) {
        log.error("job was not rescheduled <{}>", firedJobExecutionContext.getJobDetail(), e);
      }
    }