Skip to content

Commit c178be0

Browse files
committed
Add overflow checking for large Durations
Fixes #215
1 parent 311ff96 commit c178be0

File tree

7 files changed

+101
-1
lines changed

7 files changed

+101
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
- Issue #310 - Added `.builder(PolicyConfig)` methods to each of the policy interfaces, to allow new policies to be built from existing config.
66
- Issue #251 - Relaxed the illegal state validation in `RetryPolicyBuilder` to allow different types of delays to be configured, replacing previous configuration. Also removed the requirement that a jitter duration be configured after a delay.
77

8+
### Bug Fixes
9+
10+
- Issue #215 - Added overflow checking for large user-provided `Duration` values.
11+
812
# 3.0
913

1014

src/main/java/dev/failsafe/CircuitBreakerBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import dev.failsafe.event.EventListener;
2020
import dev.failsafe.internal.CircuitBreakerImpl;
2121
import dev.failsafe.internal.util.Assert;
22+
import dev.failsafe.internal.util.Durations;
2223

2324
import java.time.Duration;
2425
import java.util.function.BiPredicate;
@@ -112,6 +113,7 @@ public CircuitBreakerBuilder<R> onOpen(EventListener<CircuitBreakerStateChangedE
112113
*/
113114
public CircuitBreakerBuilder<R> withDelay(Duration delay) {
114115
Assert.notNull(delay, "delay");
116+
delay = Durations.ofSafeNanos(delay);
115117
Assert.isTrue(delay.toNanos() >= 0, "delay must be >= 0");
116118
config.delay = delay;
117119
return this;

src/main/java/dev/failsafe/RetryPolicyBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import dev.failsafe.event.ExecutionScheduledEvent;
2222
import dev.failsafe.internal.RetryPolicyImpl;
2323
import dev.failsafe.internal.util.Assert;
24+
import dev.failsafe.internal.util.Durations;
2425

2526
import java.time.Duration;
2627
import java.time.temporal.ChronoUnit;
@@ -290,6 +291,8 @@ public RetryPolicyBuilder<R> withBackoff(long delay, long maxDelay, ChronoUnit c
290291
public RetryPolicyBuilder<R> withBackoff(Duration delay, Duration maxDelay, double delayFactor) {
291292
Assert.notNull(delay, "delay");
292293
Assert.notNull(maxDelay, "maxDelay");
294+
delay = Durations.ofSafeNanos(delay);
295+
maxDelay = Durations.ofSafeNanos(maxDelay);
293296
Assert.isTrue(!delay.isNegative() && !delay.isZero(), "The delay must be > 0");
294297
Assert.state(config.maxDuration == null || delay.toNanos() < config.maxDuration.toNanos(),
295298
"delay must be < the maxDuration");
@@ -319,6 +322,7 @@ public RetryPolicyBuilder<R> withBackoff(Duration delay, Duration maxDelay, doub
319322
@Override
320323
public RetryPolicyBuilder<R> withDelay(Duration delay) {
321324
Assert.notNull(delay, "delay");
325+
delay = Durations.ofSafeNanos(delay);
322326
Assert.state(config.maxDuration == null || delay.toNanos() < config.maxDuration.toNanos(),
323327
"delay must be < the maxDuration");
324328
Assert.state(config.jitter == null || delay.toNanos() >= config.jitter.toNanos(),
@@ -361,6 +365,8 @@ public RetryPolicyBuilder<R> withDelay(long delayMin, long delayMax, ChronoUnit
361365
public RetryPolicyBuilder<R> withDelay(Duration delayMin, Duration delayMax) {
362366
Assert.notNull(delayMin, "delayMin");
363367
Assert.notNull(delayMax, "delayMax");
368+
delayMin = Durations.ofSafeNanos(delayMin);
369+
delayMax = Durations.ofSafeNanos(delayMax);
364370
Assert.isTrue(!delayMin.isNegative() && !delayMin.isZero(), "delayMin must be > 0");
365371
Assert.isTrue(!delayMax.isNegative() && !delayMax.isZero(), "delayMax must be > 0");
366372
Assert.isTrue(delayMin.toNanos() < delayMax.toNanos(), "delayMin must be < delayMax");
@@ -415,6 +421,7 @@ public RetryPolicyBuilder<R> withJitter(double jitterFactor) {
415421
*/
416422
public RetryPolicyBuilder<R> withJitter(Duration jitter) {
417423
Assert.notNull(jitter, "jitter");
424+
jitter = Durations.ofSafeNanos(jitter);
418425
Assert.isTrue(jitter.toNanos() > 0, "jitter must be > 0");
419426
boolean validJitter = config.delayMin != null ?
420427
jitter.toNanos() <= config.delayMin.toNanos() :
@@ -459,6 +466,7 @@ public RetryPolicyBuilder<R> withMaxAttempts(int maxAttempts) {
459466
*/
460467
public RetryPolicyBuilder<R> withMaxDuration(Duration maxDuration) {
461468
Assert.notNull(maxDuration, "maxDuration");
469+
maxDuration = Durations.ofSafeNanos(maxDuration);
462470
Assert.state(maxDuration.toNanos() > config.delay.toNanos(), "maxDuration must be > the delay");
463471
Assert.state(config.delayMax == null || maxDuration.toNanos() > config.delayMax.toNanos(),
464472
"maxDuration must be > the max random delay");
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2011-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License
15+
*/
16+
package dev.failsafe.internal.util;
17+
18+
import java.time.Duration;
19+
20+
/**
21+
* Duration and long utilities.
22+
*/
23+
public final class Durations {
24+
static long MAX_SECONDS_PER_LONG = Long.MAX_VALUE / 1000_000_000L;
25+
static Duration MAX_SAFE_NANOS_DURATION = Duration.ofSeconds(MAX_SECONDS_PER_LONG);
26+
27+
private Durations() {
28+
}
29+
30+
/**
31+
* Returns either the {@code duration} else a Duration containing the max seconds that can safely be converted to
32+
* nanos without overflowing.
33+
*/
34+
public static Duration ofSafeNanos(Duration duration) {
35+
return duration.getSeconds() < MAX_SECONDS_PER_LONG ? duration : MAX_SAFE_NANOS_DURATION;
36+
}
37+
}

src/main/java/dev/failsafe/spi/DelayablePolicy.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import dev.failsafe.DelayablePolicyConfig;
1919
import dev.failsafe.ExecutionContext;
2020
import dev.failsafe.Policy;
21+
import dev.failsafe.internal.util.Durations;
2122

2223
import java.time.Duration;
2324

@@ -48,7 +49,7 @@ default Duration computeDelay(ExecutionContext<R> context) {
4849
delayFailure == null || (exFailure != null && delayFailure.isAssignableFrom(exFailure.getClass()));
4950
if (delayResultMatched && delayFailureMatched) {
5051
try {
51-
computed = config.getDelayFn().get(context);
52+
computed = Durations.ofSafeNanos(config.getDelayFn().get(context));
5253
} catch (Throwable e) {
5354
if (e instanceof RuntimeException)
5455
throw (RuntimeException) e;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License
15+
*/
16+
package dev.failsafe.internal.util;
17+
18+
import org.testng.annotations.Test;
19+
20+
import java.time.Duration;
21+
22+
import static dev.failsafe.internal.util.Durations.*;
23+
import static org.testng.Assert.assertEquals;
24+
25+
@Test
26+
public class DurationsTest {
27+
public void testOfSafeNanos() {
28+
assertEquals(ofSafeNanos(Duration.ofSeconds(MAX_SECONDS_PER_LONG)), MAX_SAFE_NANOS_DURATION);
29+
assertEquals(ofSafeNanos(Duration.ofSeconds(MAX_SECONDS_PER_LONG + 1)), MAX_SAFE_NANOS_DURATION);
30+
assertEquals(ofSafeNanos(Duration.ofSeconds(Long.MAX_VALUE)), MAX_SAFE_NANOS_DURATION);
31+
Duration safeDuration = Duration.ofSeconds(MAX_SECONDS_PER_LONG - 1000);
32+
assertEquals(ofSafeNanos(safeDuration), safeDuration);
33+
}
34+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package dev.failsafe.issues;
2+
3+
import dev.failsafe.RetryPolicy;
4+
import org.testng.annotations.Test;
5+
6+
import java.time.Duration;
7+
8+
@Test
9+
public class Issue215 {
10+
public void test() {
11+
RetryPolicy.builder()
12+
.withBackoff(Duration.ofNanos(Long.MAX_VALUE).minusSeconds(1), Duration.ofSeconds(Long.MAX_VALUE), 1.50);
13+
}
14+
}

0 commit comments

Comments
 (0)