Skip to content

Commit

Permalink
Use a timer when awaiting Quartz shutdown
Browse files Browse the repository at this point in the history
The Quartz scheduler shutdown seems like it
can block indefinitely if there is an exception
in a yet to be completed job.
To guard against this, we ensure that we only
wait a configurable amount of time for the scheduler
to shut down.

This is a breaking change because previously we waited
indefinitely.

Relates to #28114
  • Loading branch information
geoand committed Sep 23, 2022
1 parent d18b476 commit cefafe8
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.quarkus.deployment.util;

import java.lang.reflect.Modifier;
import java.util.List;
import java.util.function.Predicate;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.MethodInfo;

/**
* Tests whether a class is a data class (based on this answer:
* https://discuss.kotlinlang.org/t/detect-data-class-in-runtime/6155/2)
* and whether the class has default values for fields (default values leads to having multiple constructors in bytecode)
*/
public class IsKotlinDataClassPredicate implements Predicate<ClassInfo> {

private final boolean testForDefaultValues;

public IsKotlinDataClassPredicate(boolean testForDefaultValues) {
this.testForDefaultValues = testForDefaultValues;
}

@Override
public boolean test(ClassInfo classInfo) {
int ctorCount = 0;
boolean hasCopyMethod = false;
boolean hasStaticCopyMethod = false;
boolean hasComponent1Method = false;
List<MethodInfo> methods = classInfo.methods();
for (MethodInfo method : methods) {
String methodName = method.name();
if ("<init>".equals(methodName)) {
ctorCount++;
} else if ("component1".equals(methodName) && Modifier.isFinal(method.flags())) {
hasComponent1Method = true;
} else if ("copy".equals(methodName) && Modifier.isFinal(method.flags())) {
hasCopyMethod = true;
} else if ("copy$default".equals(methodName) && Modifier.isStatic(method.flags())) {
hasStaticCopyMethod = true;
}
}

boolean isDataClass = hasComponent1Method && hasCopyMethod && hasStaticCopyMethod;
if (testForDefaultValues) {
return ctorCount > 1 && isDataClass;
} else {
return isDataClass;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.quartz.runtime;

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

Expand Down Expand Up @@ -57,6 +58,13 @@ public class QuartzBuildTimeConfig {
@ConfigItem(defaultValue = "QRTZ_")
public String tablePrefix;

/**
* The maximum amount of time Quarkus will wait for currently running jobs to finish.
* If the value is {@code 0}, then Quarkus will not wait at all for these jobs.
*/
@ConfigItem(defaultValue = "PT10S")
public Duration shutdownWaitTime;

/**
* The SQL string that selects a row in the "LOCKS" table and places a lock on the row.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
import java.util.Objects;
import java.util.OptionalLong;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.annotation.PreDestroy;
Expand Down Expand Up @@ -89,6 +92,7 @@ public class QuartzScheduler implements Scheduler {
private final org.quartz.Scheduler scheduler;
private final boolean enabled;
private final boolean startHalted;
private final Duration shutdownWaitTime;
private final Map<String, QuartzTrigger> scheduledTasks = new HashMap<>();

public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, SchedulerRuntimeConfig schedulerRuntimeConfig,
Expand All @@ -97,6 +101,7 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc
Vertx vertx) {
enabled = schedulerRuntimeConfig.enabled;
final Duration defaultOverdueGracePeriod = schedulerRuntimeConfig.overdueGracePeriod;
shutdownWaitTime = quartzSupport.getBuildTimeConfig().shutdownWaitTime;
final QuartzRuntimeConfig runtimeConfig = quartzSupport.getRuntimeConfig();

boolean forceStart;
Expand Down Expand Up @@ -478,9 +483,24 @@ void start(@Observes @Priority(Interceptor.Priority.PLATFORM_BEFORE) StartupEven
void destroy(@Observes @BeforeDestroyed(ApplicationScoped.class) Object event) {
if (scheduler != null) {
try {
// Note that this method does not return until all currently executing jobs have completed
scheduler.shutdown(true);
} catch (SchedulerException e) {
if (shutdownWaitTime.isZero()) {
scheduler.shutdown(false);
} else {
CompletableFuture.supplyAsync(new Supplier<>() {
@Override
public Void get() {
// Note that this method does not return until all currently executing jobs have completed
try {
scheduler.shutdown(true);
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
return null;
}
}).get(shutdownWaitTime.toMillis(), TimeUnit.MILLISECONDS);
}

} catch (Exception e) {
LOGGER.warnf("Unable to gracefully shutdown the scheduler", e);
}
}
Expand Down

0 comments on commit cefafe8

Please sign in to comment.