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 17, 2022
1 parent 9b9623c commit 222682f
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
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.logging.Logger;
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 {

private static final Logger LOGGER = Logger.getLogger(MisfirePolicyTest.class.getName());

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Jobs.class)
.addAsResource(new StringAsset(
"quarkus.quartz.\"simple_ignore_misfire_policy\".misfire-policy=IGNORE_MISFIRE_POLICY\n" +
"quarkus.quartz.\"cron_ignore_misfire_policy\".misfire-policy=IGNORE_MISFIRE_POLICY\n" +
"quarkus.quartz.\"simple_invalid_misfire_policy\".misfire-policy=CRON_TRIGGER_DO_NOTHING\n"
+
"quarkus.quartz.\"cron_invalid_misfire_policy\".misfire-policy=SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT\n"
+
"quarkus.quartz.\"simple_reschedule_now_existing_policy\".misfire-policy=SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT\n"
+
"quarkus.quartz.\"simple_reschedule_now_remaining_policy\".misfire-policy=SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT\n"
+
"quarkus.quartz.\"simple_reschedule_next_existing_policy\".misfire-policy=SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT\n"
+
"quarkus.quartz.\"simple_reschedule_next_remaining_policy\".misfire-policy=SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT\n"
+
"quarkus.quartz.\"cron_do_nothing_policy\".misfire-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 testInvalidMisfirePolicy() throws SchedulerException {
Trigger simpleInvalidMisfirePolicyTrigger = quartz
.getTrigger(new TriggerKey("simple_invalid_misfire_policy", Scheduler.class.getName()));
assertNotNull(simpleInvalidMisfirePolicyTrigger);
assertEquals(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY, simpleInvalidMisfirePolicyTrigger.getMisfireInstruction());

Trigger cronInvalidMisfirePolicyTrigger = quartz
.getTrigger(new TriggerKey("cron_invalid_misfire_policy", Scheduler.class.getName()));
assertNotNull(cronInvalidMisfirePolicyTrigger);
assertEquals(Trigger.MISFIRE_INSTRUCTION_SMART_POLICY, cronInvalidMisfirePolicyTrigger.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_invalid_misfire_policy", every = "1s")
void simpleInvalidMisfirePolicy() {
}

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

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

/**
* per job configuration.
*/
@ConfigDocSection
@ConfigDocMapKey("job-identity")
@ConfigItem(name = ConfigItem.PARENT)
public Map<String, QuartzNamedRuntimeConfig> namedJobs;

@ConfigGroup
public static class QuartzNamedRuntimeConfig {
/**
* The quartz misfire policy for this job.
*/
@ConfigItem(defaultValue = "SMART_POLICY")
public QuartzMisfirePolicy misfirePolicy;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package io.quarkus.quartz.runtime;

import static io.quarkus.quartz.runtime.QuartzMisfirePolicy.CRON_TRIGGER_DO_NOTHING;
import static io.quarkus.quartz.runtime.QuartzMisfirePolicy.SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT;
import static io.quarkus.quartz.runtime.QuartzMisfirePolicy.SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT;
import static io.quarkus.quartz.runtime.QuartzMisfirePolicy.SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;
import static io.quarkus.quartz.runtime.QuartzMisfirePolicy.SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
Expand All @@ -11,6 +17,7 @@
import java.util.Objects;
import java.util.OptionalLong;
import java.util.Properties;
import java.util.Set;

import javax.annotation.PreDestroy;
import javax.annotation.Priority;
Expand Down Expand Up @@ -154,7 +161,7 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc
.usingJobData(INVOKER_KEY, method.getInvokerClassName())
.requestRecovery();
ScheduleBuilder<?> scheduleBuilder;

String cron = SchedulerUtils.lookUpPropertyValue(scheduled.cron());
if (!cron.isEmpty()) {
if (SchedulerUtils.isOff(cron)) {
Expand All @@ -174,15 +181,66 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc
break;
}
}
scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
if (runtimeConfig.namedJobs.containsKey(identity)) {
switch (runtimeConfig.namedJobs.get(identity).misfirePolicy) {
case IGNORE_MISFIRE_POLICY:
cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires();
break;
case FIRE_NOW:
cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed();
break;
case CRON_TRIGGER_DO_NOTHING:
cronScheduleBuilder.withMisfireHandlingInstructionDoNothing();
}
if (Set.of(
SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT,
SIMPLE_TRIGGER_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT,
SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_EXISTING_COUNT,
SIMPLE_TRIGGER_RESCHEDULE_NEXT_WITH_REMAINING_COUNT)
.contains(runtimeConfig.namedJobs.get(identity).misfirePolicy)) {
LOGGER.warnf(
"configured cron job %s with invalid misfire policy. defaulting to SMART_POLICY",
identity);
}
}
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 (runtimeConfig.namedJobs.containsKey(identity)) {
switch (runtimeConfig.namedJobs.get(identity).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;
}
if (CRON_TRIGGER_DO_NOTHING.equals(runtimeConfig.namedJobs.get(identity).misfirePolicy)) {
LOGGER.warnf(
"configured simple job %s with invalid misfire policy. defaulting to SMART_POLICY",
identity);
}
}
scheduleBuilder = simpleScheduleBuilder;
} else {
throw new IllegalArgumentException("Invalid schedule configuration: " + scheduled);
}
Expand Down

0 comments on commit 222682f

Please sign in to comment.