Skip to content

Commit

Permalink
Allow to configure Quartz misfire handling strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
rmanibus committed Mar 18, 2022
1 parent 9b9623c commit 3f1926a
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 3 deletions.
10 changes: 10 additions & 0 deletions docs/src/main/asciidoc/quartz.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,20 @@ public class TaskBean {
}
}
----

[source,conf]
----
quarkus.quartz.misfire-policy.task-job=ignore-misfire-policy <5>
----

<1> Declare the bean in the _application_ scope
<2> Use the `@Scheduled` annotation to instruct Quarkus to run this method every 10 seconds and set the unique identifier for this job.
<3> Create a new `Task` with the current start time.
<4> Persist the task in database using xref:hibernate-orm-panache.adoc[Panache].
<5> The misfire policy can be configured for each job.

Valid misfire policy for cron jobs are: smart-policy, ignore-misfire-policy, fire-now and cron-trigger-do-nothing.
Valid misfire policy for interval jobs are: smart-policy, ignore-misfire-policy, fire-now, simple-trigger-reschedule-now-with-existing-repeat-count, simple-trigger-reschedule-now-with-remaining-repeat-count, simple-trigger-reschedule-next-with-existing-count and simple-trigger-reschedule-next-with-remaining-count.

=== Scheduling Jobs Programmatically

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.quarkus.quartz.test;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.test.QuarkusUnitTest;

public class InvalidMisfirePolicyTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setExpectedException(IllegalArgumentException.class)
.withApplicationRoot((jar) -> jar
.addClasses(MisfirePolicyTest.Jobs.class)
.addAsResource(new StringAsset(
"quarkus.quartz.misfire-policy.\"simple_invalid_misfire_policy\"=cron-trigger-do-nothing\n" +
"quarkus.quartz.misfire-policy.\"cron_invalid_misfire_policy\"=simple-trigger-reschedule-now-with-existing-repeat-count\n"),
"application.properties"));

@Test
public void shouldFailWhenInvalidTriggerListenerConfiguration() {
Assertions.fail();
}

static class Jobs {

@Scheduled(identity = "simple_invalid_misfire_policy", every = "1s")
void simpleInvalidMisfirePolicy() {
}

@Scheduled(identity = "cron_invalid_misfire_policy", cron = "0/1 * * * * ?")
void cronInvalidMisfirePolicy() {
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package io.quarkus.quartz.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import javax.inject.Inject;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.quartz.CronTrigger;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerKey;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.scheduler.Scheduler;
import io.quarkus.test.QuarkusUnitTest;

public class MisfirePolicyTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Jobs.class)
.addAsResource(new StringAsset(
"quarkus.quartz.misfire-policy.\"simple_ignore_misfire_policy\"=ignore-misfire-policy\n" +
"quarkus.quartz.misfire-policy.\"cron_ignore_misfire_policy\"=ignore-misfire-policy\n" +
"quarkus.quartz.misfire-policy.\"simple_reschedule_now_existing_policy\"=simple-trigger-reschedule-now-with-existing-repeat-count\n"
+
"quarkus.quartz.misfire-policy.\"simple_reschedule_now_remaining_policy\"=simple-trigger-reschedule-now-with-remaining-repeat-count\n"
+
"quarkus.quartz.misfire-policy.\"simple_reschedule_next_existing_policy\"=simple-trigger-reschedule-next-with-existing-count\n"
+
"quarkus.quartz.misfire-policy.\"simple_reschedule_next_remaining_policy\"=simple-trigger-reschedule-next-with-remaining-count\n"
+
"quarkus.quartz.misfire-policy.\"cron_do_nothing_policy\"=cron-trigger-do-nothing\n"

), "application.properties"));

@Inject
org.quartz.Scheduler quartz;

@Test
public void testDefaultMisfirePolicy() throws SchedulerException {
Trigger defaultMisfirePolicy = quartz
.getTrigger(new TriggerKey("default_misfire_policy", Scheduler.class.getName()));
assertNotNull(defaultMisfirePolicy);
assertEquals(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY, defaultMisfirePolicy.getMisfireInstruction());

}

@Test
public void testIgnoreMisfirePolicy() throws SchedulerException {
Trigger simpleIgnoreMisfirePolicyTrigger = quartz
.getTrigger(new TriggerKey("simple_ignore_misfire_policy", Scheduler.class.getName()));
assertNotNull(simpleIgnoreMisfirePolicyTrigger);
assertEquals(Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY,
simpleIgnoreMisfirePolicyTrigger.getMisfireInstruction());

Trigger cronIgnoreMisfirePolicyTrigger = quartz
.getTrigger(new TriggerKey("cron_ignore_misfire_policy", Scheduler.class.getName()));
assertNotNull(cronIgnoreMisfirePolicyTrigger);
assertEquals(Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY, cronIgnoreMisfirePolicyTrigger.getMisfireInstruction());
}

@Test
public void testSimpleTriggerMisfirePolicy() throws SchedulerException {
Trigger simpleRescheduleNowExistingPolicy = quartz
.getTrigger(new TriggerKey("simple_reschedule_now_existing_policy", Scheduler.class.getName()));
assertNotNull(simpleRescheduleNowExistingPolicy);
assertEquals(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT,
simpleRescheduleNowExistingPolicy.getMisfireInstruction());

Trigger simpleRescheduleNowRemainingPolicy = quartz
.getTrigger(new TriggerKey("simple_reschedule_now_remaining_policy", Scheduler.class.getName()));
assertNotNull(simpleRescheduleNowRemainingPolicy);
assertEquals(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT,
simpleRescheduleNowRemainingPolicy.getMisfireInstruction());

Trigger simpleRescheduleNextExistingPolicy = quartz
.getTrigger(new TriggerKey("simple_reschedule_next_existing_policy", Scheduler.class.getName()));
assertNotNull(simpleRescheduleNextExistingPolicy);
assertEquals(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT,
simpleRescheduleNextExistingPolicy.getMisfireInstruction());

Trigger simpleRescheduleNextRemainingPolicy = quartz
.getTrigger(new TriggerKey("simple_reschedule_next_remaining_policy", Scheduler.class.getName()));
assertNotNull(simpleRescheduleNextRemainingPolicy);
assertEquals(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT,
simpleRescheduleNextRemainingPolicy.getMisfireInstruction());
}

@Test
public void testCronTriggerMisfirePolicy() throws SchedulerException {
Trigger cronDoNothingPolicy = quartz.getTrigger(new TriggerKey("cron_do_nothing_policy", Scheduler.class.getName()));
assertNotNull(cronDoNothingPolicy);
assertEquals(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING, cronDoNothingPolicy.getMisfireInstruction());
}

static class Jobs {

@Scheduled(identity = "default_misfire_policy", every = "1s")
void defaultMisfirePolicy() {
}

@Scheduled(identity = "simple_ignore_misfire_policy", every = "1s")
void simpleIgnoreMisfirePolicy() {
}

@Scheduled(identity = "cron_ignore_misfire_policy", cron = "0/1 * * * * ?")
void cronIgnoreMisfirePolicy() {
}

@Scheduled(identity = "simple_reschedule_now_existing_policy", every = "1s")
void simpleRescheduleNowExistingPolicy() {
}

@Scheduled(identity = "simple_reschedule_now_remaining_policy", every = "1s")
void simpleRescheduleNowRemainingPolicy() {
}

@Scheduled(identity = "simple_reschedule_next_existing_policy", every = "1s")
void simpleRescheduleNextExistingPolicy() {
}

@Scheduled(identity = "simple_reschedule_next_remaining_policy", every = "1s")
void simpleRescheduleNextRemainingPolicy() {
}

@Scheduled(identity = "cron_do_nothing_policy", cron = "0/1 * * * * ?")
void cronDoNothingPolicy() {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkus.quartz.runtime;

public enum QuartzMisfirePolicy {
SMART_POLICY,
IGNORE_MISFIRE_POLICY,
FIRE_NOW,
SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT,
SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT,
SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT,
SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT,
CRON_TRIGGER_DO_NOTHING
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package io.quarkus.quartz.runtime;

import java.time.Duration;
import java.util.Map;

import io.quarkus.runtime.annotations.ConfigDocMapKey;
import io.quarkus.runtime.annotations.ConfigDocSection;
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
Expand Down Expand Up @@ -45,4 +49,22 @@ public class QuartzRuntimeConfig {
*/
@ConfigItem(defaultValue = "normal")
public QuartzStartMode startMode;

/**
* Misfire policy per job configuration.
*/
@ConfigDocSection
@ConfigDocMapKey("identity")
@ConfigItem(name = "misfire-policy")
public Map<String, QuartzMisfirePolicyConfig> misfirePolicyPerJobs;

@ConfigGroup
public static class QuartzMisfirePolicyConfig {
/**
* The quartz misfire policy for this job.
*/
@ConfigItem(defaultValue = "smart-policy", name = ConfigItem.PARENT)
public QuartzMisfirePolicy misfirePolicy;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc
.usingJobData(INVOKER_KEY, method.getInvokerClassName())
.requestRecovery();
ScheduleBuilder<?> scheduleBuilder;

QuartzRuntimeConfig.QuartzMisfirePolicyConfig perJobConfig = runtimeConfig.misfirePolicyPerJobs
.get(identity);
String cron = SchedulerUtils.lookUpPropertyValue(scheduled.cron());
if (!cron.isEmpty()) {
if (SchedulerUtils.isOff(cron)) {
Expand All @@ -174,15 +175,64 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc
break;
}
}
scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
if (perJobConfig != null) {
switch (perJobConfig.misfirePolicy) {
case IGNORE_MISFIRE_POLICY:
cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires();
break;
case FIRE_NOW:
cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed();
break;
case CRON_TRIGGER_DO_NOTHING:
cronScheduleBuilder.withMisfireHandlingInstructionDoNothing();
break;
case SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT:
case SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT:
case SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT:
case SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT:
throw new IllegalArgumentException("configured cron job " + identity
+ " with invalid misfire policy " + perJobConfig.misfirePolicy +
"\nvalid options are: IGNORE_MISFIRE_POLICY, FIRE_NOW, CRON_TRIGGER_DO_NOTHING");

}
}
scheduleBuilder = cronScheduleBuilder;
} else if (!scheduled.every().isEmpty()) {
OptionalLong everyMillis = SchedulerUtils.parseEveryAsMillis(scheduled);
if (!everyMillis.isPresent()) {
continue;
}
scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(everyMillis.getAsLong())
.repeatForever();
if (perJobConfig != null) {
switch (perJobConfig.misfirePolicy) {
case IGNORE_MISFIRE_POLICY:
simpleScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires();
break;
case FIRE_NOW:
simpleScheduleBuilder.withMisfireHandlingInstructionFireNow();
break;
case SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT:
simpleScheduleBuilder.withMisfireHandlingInstructionNowWithExistingCount();
break;
case SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT:
simpleScheduleBuilder.withMisfireHandlingInstructionNowWithRemainingCount();
break;
case SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT:
simpleScheduleBuilder.withMisfireHandlingInstructionNextWithExistingCount();
break;
case SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT:
simpleScheduleBuilder.withMisfireHandlingInstructionNextWithRemainingCount();
break;
case CRON_TRIGGER_DO_NOTHING:
throw new IllegalArgumentException("configured simple job " + identity
+ " with invalid misfire policy CRON_TRIGGER_DO_NOTHING" +
"\nvalid options are: IGNORE_MISFIRE_POLICY, FIRE_NOW, SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT, SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT, SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT and SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT");
}
}
scheduleBuilder = simpleScheduleBuilder;
} else {
throw new IllegalArgumentException("Invalid schedule configuration: " + scheduled);
}
Expand Down

0 comments on commit 3f1926a

Please sign in to comment.