Skip to content

Commit

Permalink
Avoid arithmetic overflow for large delay/period values
Browse files Browse the repository at this point in the history
Closes gh-30754
  • Loading branch information
jhoeller committed Jun 26, 2023
1 parent 449174c commit 599ac58
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 23 deletions.
Expand Up @@ -70,6 +70,9 @@
*/
public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements TaskScheduler {

private static final TimeUnit NANO = TimeUnit.NANOSECONDS;


@Nullable
private static Class<?> managedScheduledExecutorServiceClass;

Expand Down Expand Up @@ -211,7 +214,8 @@ public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay.toNanos(), TimeUnit.NANOSECONDS);
return this.scheduledExecutor.schedule(decorateTask(task, false),
NANO.convert(initialDelay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
Expand All @@ -222,7 +226,8 @@ public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period) {
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay.toNanos(), period.toNanos(), TimeUnit.NANOSECONDS);
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true),
NANO.convert(initialDelay), NANO.convert(period), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
Expand All @@ -232,7 +237,8 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime,
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
try {
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), 0, period.toNanos(), TimeUnit.NANOSECONDS);
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true),
0, NANO.convert(period), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
Expand All @@ -243,7 +249,8 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay) {
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay.toNanos(), delay.toNanos(), TimeUnit.NANOSECONDS);
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true),
NANO.convert(initialDelay), NANO.convert(delay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
Expand All @@ -253,7 +260,8 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTim
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
try {
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), 0, delay.toNanos(), TimeUnit.NANOSECONDS);
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true),
0, NANO.convert(delay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
Expand Down
Expand Up @@ -63,6 +63,9 @@
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {

private static final TimeUnit NANO = TimeUnit.NANOSECONDS;


private volatile int poolSize = 1;

private volatile boolean removeOnCancelPolicy;
Expand Down Expand Up @@ -382,7 +385,8 @@ public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
ScheduledExecutorService executor = getScheduledExecutor();
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return executor.schedule(errorHandlingTask(task, false), initialDelay.toNanos(), TimeUnit.NANOSECONDS);
return executor.schedule(errorHandlingTask(task, false),
NANO.convert(initialDelay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
Expand All @@ -394,7 +398,8 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime,
ScheduledExecutorService executor = getScheduledExecutor();
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), initialDelay.toNanos(), period.toNanos(), TimeUnit.NANOSECONDS);
return executor.scheduleAtFixedRate(errorHandlingTask(task, true),
NANO.convert(initialDelay), NANO.convert(period), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
Expand All @@ -405,7 +410,8 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime,
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), 0, period.toNanos(), TimeUnit.NANOSECONDS);
return executor.scheduleAtFixedRate(errorHandlingTask(task, true),
0, NANO.convert(period), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
Expand All @@ -417,7 +423,8 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTim
ScheduledExecutorService executor = getScheduledExecutor();
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), initialDelay.toNanos(), delay.toNanos(), TimeUnit.NANOSECONDS);
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true),
NANO.convert(initialDelay), NANO.convert(delay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
Expand All @@ -428,7 +435,8 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTim
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), 0, delay.toNanos(), TimeUnit.NANOSECONDS);
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true),
0, NANO.convert(delay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -95,6 +95,7 @@ void closeContextAfterTest() {
FixedDelay, 5_000
FixedDelayInSeconds, 5_000
FixedDelayInMinutes, 180_000
FixedDelayWithMaxValue, -1
""")
void fixedDelayTask(@NameToClass Class<?> beanClass, long expectedInterval) {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
Expand All @@ -120,7 +121,8 @@ void fixedDelayTask(@NameToClass Class<?> beanClass, long expectedInterval) {
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedDelay");
assertThat(task.getInitialDelayDuration()).isZero();
assertThat(task.getIntervalDuration()).isEqualTo(Duration.ofMillis(expectedInterval));
assertThat(task.getIntervalDuration()).isEqualTo(
Duration.ofMillis(expectedInterval < 0 ? Long.MAX_VALUE : expectedInterval));
}

@ParameterizedTest
Expand Down Expand Up @@ -343,8 +345,7 @@ void cronTaskWithInvalidZone() {
BeanDefinition targetDefinition = new RootBeanDefinition(CronWithInvalidTimezoneTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(context::refresh);
}

@Test
Expand All @@ -355,8 +356,7 @@ void cronTaskWithMethodValidation() {
context.registerBeanDefinition("methodValidation", validationDefinition);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(context::refresh);
}

@Test
Expand Down Expand Up @@ -702,18 +702,16 @@ void emptyAnnotation() {
BeanDefinition targetDefinition = new RootBeanDefinition(EmptyAnnotationTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(context::refresh);
}

@Test
void invalidCron() throws Throwable {
void invalidCron() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(InvalidCronTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(context::refresh);
}

@Test
Expand All @@ -722,8 +720,7 @@ void nonEmptyParamList() {
BeanDefinition targetDefinition = new RootBeanDefinition(NonEmptyParamListTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(context::refresh);
}


Expand All @@ -748,6 +745,13 @@ void fixedDelay() {
}
}

static class FixedDelayWithMaxValue {

@Scheduled(fixedDelay = Long.MAX_VALUE)
void fixedDelay() {
}
}


static class FixedRate {

Expand Down

0 comments on commit 599ac58

Please sign in to comment.