This is an automated email from the ASF dual-hosted git repository.
merlimat pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar.git
The following commit(s) were added to refs/heads/master by this push:
new ff67bb2042e [improve][client] PIP-468: Make Backoff jitter
configurable (#25669)
ff67bb2042e is described below
commit ff67bb2042ef855196d06e2cff94ba81585febbf
Author: Matteo Merli <[email protected]>
AuthorDate: Tue May 5 18:32:59 2026 -0700
[improve][client] PIP-468: Make Backoff jitter configurable (#25669)
---
.../pulsar/client/api/v5/config/BackoffPolicy.java | 57 +++++++---
.../pulsar/client/impl/ConsumerImplTest.java | 2 +-
.../org/apache/pulsar/common/util/Backoff.java | 50 ++++++---
.../org/apache/pulsar/common/util/BackoffTest.java | 117 +++++++++++++++------
4 files changed, 170 insertions(+), 56 deletions(-)
diff --git
a/pulsar-client-api-v5/src/main/java/org/apache/pulsar/client/api/v5/config/BackoffPolicy.java
b/pulsar-client-api-v5/src/main/java/org/apache/pulsar/client/api/v5/config/BackoffPolicy.java
index bca04a2a596..963b5ec8238 100644
---
a/pulsar-client-api-v5/src/main/java/org/apache/pulsar/client/api/v5/config/BackoffPolicy.java
+++
b/pulsar-client-api-v5/src/main/java/org/apache/pulsar/client/api/v5/config/BackoffPolicy.java
@@ -26,7 +26,9 @@ import lombok.ToString;
/**
* Backoff configuration for broker reconnection attempts.
*
- * <p>The delay for attempt {@code n} is {@code min(initialInterval *
multiplier^(n-1), maxInterval)}.
+ * <p>The base delay for attempt {@code n} is {@code min(initialInterval *
multiplier^(n-1), maxInterval)}.
+ * A symmetric random jitter of {@code ±jitterPercent/2} is applied to each
delay (including the
+ * first one) to spread out concurrent retries.
*
* <p>Use {@link #fixed(Duration, Duration)} or {@link #exponential(Duration,
Duration)} for
* the common cases, or {@link #builder()} to configure all knobs explicitly.
@@ -35,23 +37,31 @@ import lombok.ToString;
@ToString
public final class BackoffPolicy {
+ /** Default jitter percentage applied when not explicitly specified. */
+ public static final double DEFAULT_JITTER_PERCENT = 10.0;
+
private final Duration initialInterval;
private final Duration maxInterval;
private final double multiplier;
+ private final double jitterPercent;
- private BackoffPolicy(Duration initialInterval, Duration maxInterval,
double multiplier) {
+ private BackoffPolicy(Duration initialInterval, Duration maxInterval,
double multiplier, double jitterPercent) {
Objects.requireNonNull(initialInterval, "initialInterval must not be
null");
Objects.requireNonNull(maxInterval, "maxInterval must not be null");
if (multiplier < 1.0) {
throw new IllegalArgumentException("multiplier must be >= 1.0");
}
+ if (jitterPercent < 0 || jitterPercent > 100) {
+ throw new IllegalArgumentException("jitterPercent must be in [0,
100]");
+ }
this.initialInterval = initialInterval;
this.maxInterval = maxInterval;
this.multiplier = multiplier;
+ this.jitterPercent = jitterPercent;
}
/**
- * @return the delay before the first reconnection attempt
+ * @return the base delay before the first reconnection attempt
*/
public Duration initialInterval() {
return initialInterval;
@@ -72,25 +82,33 @@ public final class BackoffPolicy {
}
/**
- * Create a fixed backoff (no increase between retries).
+ * @return the symmetric jitter percentage applied to each delay; {@code
0} means no jitter
+ */
+ public double jitterPercent() {
+ return jitterPercent;
+ }
+
+ /**
+ * Create a fixed backoff (no increase between retries) with the default
jitter.
*
- * @param initialInterval the constant delay between reconnection attempts
+ * @param initialInterval the constant base delay between reconnection
attempts
* @param maxInterval the maximum delay between reconnection attempts
- * @return a {@link BackoffPolicy} with a multiplier of 1.0
+ * @return a {@link BackoffPolicy} with a multiplier of 1.0 and the
default jitter
*/
public static BackoffPolicy fixed(Duration initialInterval, Duration
maxInterval) {
- return new BackoffPolicy(initialInterval, maxInterval, 1.0);
+ return new BackoffPolicy(initialInterval, maxInterval, 1.0,
DEFAULT_JITTER_PERCENT);
}
/**
- * Create an exponential backoff with the given bounds and a default
multiplier of 2.
+ * Create an exponential backoff with the given bounds, a default
multiplier of 2 and the
+ * default jitter.
*
- * @param initialInterval the delay before the first reconnection attempt
+ * @param initialInterval the base delay before the first reconnection
attempt
* @param maxInterval the maximum delay between reconnection attempts
- * @return a {@link BackoffPolicy} with a multiplier of 2.0
+ * @return a {@link BackoffPolicy} with a multiplier of 2.0 and the
default jitter
*/
public static BackoffPolicy exponential(Duration initialInterval, Duration
maxInterval) {
- return new BackoffPolicy(initialInterval, maxInterval, 2.0);
+ return new BackoffPolicy(initialInterval, maxInterval, 2.0,
DEFAULT_JITTER_PERCENT);
}
/**
@@ -107,6 +125,7 @@ public final class BackoffPolicy {
private Duration initialInterval;
private Duration maxInterval;
private double multiplier = 2.0;
+ private double jitterPercent = DEFAULT_JITTER_PERCENT;
private Builder() {
}
@@ -145,11 +164,25 @@ public final class BackoffPolicy {
return this;
}
+ /**
+ * Symmetric jitter percentage applied to each returned delay. The
actual jitter is the
+ * base delay multiplied by a uniform random factor in
+ * {@code [1 - jitterPercent/200, 1 + jitterPercent/200)}. Default is
{@code 10.0}; set to
+ * {@code 0} to disable jitter.
+ *
+ * @param jitterPercent the jitter percentage, must be in {@code [0,
100]}
+ * @return this builder
+ */
+ public Builder jitterPercent(double jitterPercent) {
+ this.jitterPercent = jitterPercent;
+ return this;
+ }
+
/**
* @return a new {@link BackoffPolicy} instance
*/
public BackoffPolicy build() {
- return new BackoffPolicy(initialInterval, maxInterval, multiplier);
+ return new BackoffPolicy(initialInterval, maxInterval, multiplier,
jitterPercent);
}
}
}
diff --git
a/pulsar-client/src/test/java/org/apache/pulsar/client/impl/ConsumerImplTest.java
b/pulsar-client/src/test/java/org/apache/pulsar/client/impl/ConsumerImplTest.java
index f53051e2c42..9d8b59db910 100644
---
a/pulsar-client/src/test/java/org/apache/pulsar/client/impl/ConsumerImplTest.java
+++
b/pulsar-client/src/test/java/org/apache/pulsar/client/impl/ConsumerImplTest.java
@@ -111,7 +111,7 @@ public class ConsumerImplTest {
ClientConfigurationData clientConfigurationData = new
ClientConfigurationData();
Assert.assertEquals(backoff.getMax().toMillis(),
TimeUnit.NANOSECONDS.toMillis(clientConfigurationData.getMaxBackoffIntervalNanos()));
- Assert.assertEquals(backoff.next().toMillis(),
+ Assert.assertEquals(backoff.getInitial().toMillis(),
TimeUnit.NANOSECONDS.toMillis(clientConfigurationData.getInitialBackoffIntervalNanos()));
}
diff --git
a/pulsar-common/src/main/java/org/apache/pulsar/common/util/Backoff.java
b/pulsar-common/src/main/java/org/apache/pulsar/common/util/Backoff.java
index 5957ce86796..8bcc165dd61 100644
--- a/pulsar-common/src/main/java/org/apache/pulsar/common/util/Backoff.java
+++ b/pulsar-common/src/main/java/org/apache/pulsar/common/util/Backoff.java
@@ -28,8 +28,8 @@ import lombok.Getter;
* Exponential backoff with mandatory stop.
*
* <p>Delays start at {@code initialDelay} and double on every call to {@link
#next()}, up to
- * {@code maxBackoff}. A random jitter of up to 10% is subtracted from each
value to avoid
- * thundering-herd retries.
+ * {@code maxBackoff}. A symmetric random jitter of {@code ±jitterPercent/2}
is applied to every
+ * returned value (including the first one) to avoid thundering-herd retries.
*
* <p>If a {@code mandatoryStop} duration is configured, the backoff tracks
wall-clock time from the
* first {@link #next()} call. Once the elapsed time plus the next delay would
exceed the mandatory
@@ -43,6 +43,7 @@ import lombok.Getter;
* .initialDelay(Duration.ofMillis(100))
* .maxBackoff(Duration.ofMinutes(1))
* .mandatoryStop(Duration.ofSeconds(30))
+ * .jitterPercent(10.0)
* .build();
*
* Duration delay = backoff.next();
@@ -51,6 +52,7 @@ import lombok.Getter;
public class Backoff {
private static final Duration DEFAULT_INITIAL_DELAY =
Duration.ofMillis(100);
private static final Duration DEFAULT_MAX_BACKOFF_INTERVAL =
Duration.ofMinutes(1);
+ private static final double DEFAULT_JITTER_PERCENT = 10.0;
private static final Random random = new Random();
@Getter
@@ -59,6 +61,8 @@ public class Backoff {
private final Duration max;
@Getter
private final Duration mandatoryStop;
+ @Getter
+ private final double jitterPercent;
private final Clock clock;
private Duration next;
@@ -67,10 +71,11 @@ public class Backoff {
@Getter
private boolean mandatoryStopMade;
- private Backoff(Duration initial, Duration max, Duration mandatoryStop,
Clock clock) {
+ private Backoff(Duration initial, Duration max, Duration mandatoryStop,
double jitterPercent, Clock clock) {
this.initial = initial;
this.max = max;
this.mandatoryStop = mandatoryStop;
+ this.jitterPercent = jitterPercent;
this.next = initial;
this.clock = clock;
this.firstBackoffTime = Instant.EPOCH;
@@ -101,8 +106,10 @@ public class Backoff {
/**
* Returns the next backoff delay, advancing the internal state.
*
- * <p>The returned duration is never less than the initial delay and never
more than the max
- * backoff. A random jitter of up to 10% is subtracted to spread out
concurrent retries.
+ * <p>The underlying delay starts at the initial delay and doubles on each
call up to the max
+ * backoff. A symmetric jitter of {@code ±jitterPercent/2} is applied on
every call (including
+ * the first one) to spread out concurrent retries; the returned value may
therefore be slightly
+ * below the initial delay or slightly above the max backoff.
*
* @return the delay to wait before the next retry attempt
*/
@@ -130,13 +137,13 @@ public class Backoff {
}
}
- // Randomly decrease the timeout up to 10% to avoid simultaneous
retries
long currentMillis = current.toMillis();
- if (currentMillis > 10) {
- currentMillis -= random.nextInt((int) currentMillis / 10);
+ if (jitterPercent > 0 && currentMillis > 0) {
+ // Apply a symmetric jitter of ±jitterPercent/2 around the current
delay.
+ double factor = 1.0 + (random.nextDouble() - 0.5) * (jitterPercent
/ 100.0);
+ currentMillis = Math.max(0L, Math.round(currentMillis * factor));
}
- long initialMillis = initial.toMillis();
- return Duration.ofMillis(Math.max(initialMillis, currentMillis));
+ return Duration.ofMillis(currentMillis);
}
/**
@@ -162,12 +169,13 @@ public class Backoff {
/**
* Builder for {@link Backoff}.
*
- * <p>Defaults: initial delay 100 ms, max backoff 1 min, no mandatory stop.
+ * <p>Defaults: initial delay 100 ms, max backoff 1 min, no mandatory
stop, 10% jitter.
*/
public static class Builder {
private Duration initialDelay = DEFAULT_INITIAL_DELAY;
private Duration maxBackoff = DEFAULT_MAX_BACKOFF_INTERVAL;
private Duration mandatoryStop = Duration.ZERO;
+ private double jitterPercent = DEFAULT_JITTER_PERCENT;
private Clock clock = Clock.systemDefaultZone();
/**
@@ -205,6 +213,24 @@ public class Backoff {
return this;
}
+ /**
+ * Sets the jitter percentage applied to each returned delay. The
actual jitter is symmetric:
+ * the returned value is multiplied by a uniform random factor in
+ * {@code [1 - jitterPercent/200, 1 + jitterPercent/200)}. Defaults to
10. Set to 0 to disable
+ * jitter.
+ *
+ * @param jitterPercent the jitter percentage, must be in {@code [0,
100]}
+ * @return this builder
+ * @throws IllegalArgumentException if {@code jitterPercent} is
outside {@code [0, 100]}
+ */
+ public Builder jitterPercent(double jitterPercent) {
+ if (jitterPercent < 0 || jitterPercent > 100) {
+ throw new IllegalArgumentException("jitterPercent must be in
[0, 100]");
+ }
+ this.jitterPercent = jitterPercent;
+ return this;
+ }
+
Builder clock(Clock clock) {
this.clock = clock;
return this;
@@ -216,7 +242,7 @@ public class Backoff {
* @return a new Backoff
*/
public Backoff build() {
- return new Backoff(initialDelay, maxBackoff, mandatoryStop, clock);
+ return new Backoff(initialDelay, maxBackoff, mandatoryStop,
jitterPercent, clock);
}
}
}
diff --git
a/pulsar-common/src/test/java/org/apache/pulsar/common/util/BackoffTest.java
b/pulsar-common/src/test/java/org/apache/pulsar/common/util/BackoffTest.java
index 152ddee5156..41c2de63c10 100644
--- a/pulsar-common/src/test/java/org/apache/pulsar/common/util/BackoffTest.java
+++ b/pulsar-common/src/test/java/org/apache/pulsar/common/util/BackoffTest.java
@@ -29,11 +29,6 @@ import org.mockito.Mockito;
import org.testng.annotations.Test;
public class BackoffTest {
- boolean withinTenPercentAndDecrementTimer(Backoff backoff, long t2) {
- long t1 = backoff.next().toMillis();
- return (t1 >= t2 * 0.9 && t1 <= t2);
- }
-
boolean checkExactAndDecrementTimer(Backoff backoff, long t2) {
long t1 = backoff.next().toMillis();
return t1 == t2;
@@ -45,12 +40,13 @@ public class BackoffTest {
.initialDelay(Duration.ofMillis(100))
.maxBackoff(Duration.ofSeconds(60))
.mandatoryStop(Duration.ofMillis(1900))
+ .jitterPercent(0)
.build();
assertEquals(backoff.next().toMillis(), 100);
backoff.next(); // 200
backoff.next(); // 400
backoff.next(); // 800
- assertFalse(withinTenPercentAndDecrementTimer(backoff, 400));
+ assertFalse(checkExactAndDecrementTimer(backoff, 400));
}
@Test
@@ -64,6 +60,7 @@ public class BackoffTest {
.initialDelay(Duration.ofMillis(100))
.maxBackoff(Duration.ofSeconds(60))
.mandatoryStop(Duration.ofMillis(1900))
+ .jitterPercent(0)
.clock(mockClock)
.build();
@@ -83,10 +80,11 @@ public class BackoffTest {
.initialDelay(Duration.ofMillis(5))
.maxBackoff(Duration.ofSeconds(60))
.mandatoryStop(Duration.ofSeconds(60))
+ .jitterPercent(0)
.clock(mockClock)
.build();
assertTrue(checkExactAndDecrementTimer(backoff, 5));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 10));
+ assertTrue(checkExactAndDecrementTimer(backoff, 10));
backoff.reset();
assertTrue(checkExactAndDecrementTimer(backoff, 5));
}
@@ -104,13 +102,14 @@ public class BackoffTest {
.initialDelay(Duration.ofMillis(5))
.maxBackoff(Duration.ofMillis(20))
.mandatoryStop(Duration.ofMillis(20))
+ .jitterPercent(0)
.clock(mockClock)
.build();
assertTrue(checkExactAndDecrementTimer(backoff, 5));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 10));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 5));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 20));
+ assertTrue(checkExactAndDecrementTimer(backoff, 10));
+ assertTrue(checkExactAndDecrementTimer(backoff, 5));
+ assertTrue(checkExactAndDecrementTimer(backoff, 20));
}
@Test
@@ -121,70 +120,126 @@ public class BackoffTest {
.initialDelay(Duration.ofMillis(100))
.maxBackoff(Duration.ofSeconds(60))
.mandatoryStop(Duration.ofMillis(1900))
+ .jitterPercent(0)
.clock(mockClock)
.build();
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(0));
assertTrue(checkExactAndDecrementTimer(backoff, 100));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(100));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 200));
+ assertTrue(checkExactAndDecrementTimer(backoff, 200));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(300));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 400));
+ assertTrue(checkExactAndDecrementTimer(backoff, 400));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(700));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 800));
+ assertTrue(checkExactAndDecrementTimer(backoff, 800));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(1500));
// would have been 1600 w/o the mandatory stop
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 400));
+ assertTrue(checkExactAndDecrementTimer(backoff, 400));
assertTrue(backoff.isMandatoryStopMade());
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(1900));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 3200));
+ assertTrue(checkExactAndDecrementTimer(backoff, 3200));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(3200));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 6400));
+ assertTrue(checkExactAndDecrementTimer(backoff, 6400));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(3200));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 12800));
+ assertTrue(checkExactAndDecrementTimer(backoff, 12800));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(6400));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 25600));
+ assertTrue(checkExactAndDecrementTimer(backoff, 25600));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(12800));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 51200));
+ assertTrue(checkExactAndDecrementTimer(backoff, 51200));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(25600));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 60000));
+ assertTrue(checkExactAndDecrementTimer(backoff, 60000));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(51200));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 60000));
+ assertTrue(checkExactAndDecrementTimer(backoff, 60000));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(60000));
backoff.reset();
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(0));
assertTrue(checkExactAndDecrementTimer(backoff, 100));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(100));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 200));
+ assertTrue(checkExactAndDecrementTimer(backoff, 200));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(300));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 400));
+ assertTrue(checkExactAndDecrementTimer(backoff, 400));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(700));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 800));
+ assertTrue(checkExactAndDecrementTimer(backoff, 800));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(1500));
// would have been 1600 w/o the mandatory stop
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 400));
+ assertTrue(checkExactAndDecrementTimer(backoff, 400));
backoff.reset();
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(0));
assertTrue(checkExactAndDecrementTimer(backoff, 100));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(100));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 200));
+ assertTrue(checkExactAndDecrementTimer(backoff, 200));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(300));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 400));
+ assertTrue(checkExactAndDecrementTimer(backoff, 400));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(700));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 800));
+ assertTrue(checkExactAndDecrementTimer(backoff, 800));
backoff.reset();
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(0));
assertTrue(checkExactAndDecrementTimer(backoff, 100));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(100));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 200));
+ assertTrue(checkExactAndDecrementTimer(backoff, 200));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(300));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 400));
+ assertTrue(checkExactAndDecrementTimer(backoff, 400));
Mockito.when(mockClock.instant()).thenReturn(Instant.ofEpochMilli(700));
- assertTrue(withinTenPercentAndDecrementTimer(backoff, 800));
+ assertTrue(checkExactAndDecrementTimer(backoff, 800));
+ }
+
+ @Test
+ public void jitterIsAppliedSymmetricallyOnFirstCall() {
+ // With jitterPercent=20, the returned delay is in [base*0.9,
base*1.1).
+ // Verify that across many calls we observe values both below and
above the base, including
+ // on the very first call after a reset.
+ Backoff backoff = Backoff.builder()
+ .initialDelay(Duration.ofMillis(1000))
+ .maxBackoff(Duration.ofMillis(1000))
+ .jitterPercent(20)
+ .build();
+
+ boolean sawBelow = false;
+ boolean sawAbove = false;
+ long min = Long.MAX_VALUE;
+ long max = Long.MIN_VALUE;
+ for (int i = 0; i < 500; i++) {
+ backoff.reset();
+ long t = backoff.next().toMillis();
+ assertTrue(t >= 900 && t <= 1100, "value out of range: " + t);
+ if (t < 1000) {
+ sawBelow = true;
+ }
+ if (t > 1000) {
+ sawAbove = true;
+ }
+ min = Math.min(min, t);
+ max = Math.max(max, t);
+ }
+ assertTrue(sawBelow, "expected at least one jittered value below base,
min=" + min);
+ assertTrue(sawAbove, "expected at least one jittered value above base,
max=" + max);
+ }
+
+ @Test
+ public void jitterPercentZeroDisablesJitter() {
+ Backoff backoff = Backoff.builder()
+ .initialDelay(Duration.ofMillis(100))
+ .maxBackoff(Duration.ofMillis(100))
+ .jitterPercent(0)
+ .build();
+ for (int i = 0; i < 100; i++) {
+ backoff.reset();
+ assertEquals(backoff.next().toMillis(), 100);
+ }
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void negativeJitterIsRejected() {
+ Backoff.builder().jitterPercent(-1);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void jitterAboveHundredIsRejected() {
+ Backoff.builder().jitterPercent(101);
}
}