diff --git a/core/src/main/java/com/linecorp/armeria/client/retry/AbstractBackoffBuilder.java b/core/src/main/java/com/linecorp/armeria/client/retry/AbstractBackoffBuilder.java new file mode 100644 index 00000000000..c2c194f1d05 --- /dev/null +++ b/core/src/main/java/com/linecorp/armeria/client/retry/AbstractBackoffBuilder.java @@ -0,0 +1,92 @@ +/* + * Copyright 2024 LY Corporation + * + * LY Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.armeria.client.retry; + +import static java.util.Objects.requireNonNull; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Supplier; + +import com.linecorp.armeria.common.annotation.Nullable; + +/** + * A skeletal builder implementation for {@link Backoff}. + */ +abstract class AbstractBackoffBuilder> { + @Nullable + private Double minJitterRate; + @Nullable + private Double maxJitterRate; + @Nullable + private Integer maxAttempts; + @Nullable + private Supplier randomSupplier; + + @SuppressWarnings("unchecked") + private SELF self() { + return (SELF) this; + } + + /** + * Sets the minimum and maximum jitter rates to apply to the delay. + */ + public final SELF jitter(double minJitterRate, double maxJitterRate) { + this.minJitterRate = minJitterRate; + this.maxJitterRate = maxJitterRate; + return self(); + } + + /** + * Sets the minimum and maximum jitter rates to apply to the delay, as well as a + * custom {@link Random} supplier for generating the jitter. + */ + public final SELF jitter(double minJitterRate, double maxJitterRate, Supplier randomSupplier) { + requireNonNull(randomSupplier, "randomSupplier"); + this.minJitterRate = minJitterRate; + this.maxJitterRate = maxJitterRate; + this.randomSupplier = randomSupplier; + return self(); + } + + /** + * Sets the maximum number of attempts. + */ + public final SELF maxAttempts(int maxAttempts) { + this.maxAttempts = maxAttempts; + return self(); + } + + abstract Backoff doBuild(); + + /** + * Builds and returns {@link Backoff} instance with configured properties. + */ + public final Backoff build() { + Backoff backoff = doBuild(); + if (minJitterRate != null && maxJitterRate != null) { + Supplier randomSupplier = this.randomSupplier; + if (randomSupplier == null) { + randomSupplier = ThreadLocalRandom::current; + } + backoff = new JitterAddingBackoff(backoff, minJitterRate, maxJitterRate, randomSupplier); + } + if (maxAttempts != null) { + backoff = new AttemptLimitingBackoff(backoff, maxAttempts); + } + return backoff; + } +} diff --git a/core/src/main/java/com/linecorp/armeria/client/retry/Backoff.java b/core/src/main/java/com/linecorp/armeria/client/retry/Backoff.java index 6ec01aa13f1..5b8a289fd33 100644 --- a/core/src/main/java/com/linecorp/armeria/client/retry/Backoff.java +++ b/core/src/main/java/com/linecorp/armeria/client/retry/Backoff.java @@ -25,6 +25,7 @@ import com.linecorp.armeria.common.Flags; import com.linecorp.armeria.common.annotation.Nullable; +import com.linecorp.armeria.common.annotation.UnstableApi; import com.linecorp.armeria.common.util.Unwrappable; /** @@ -131,6 +132,60 @@ static Backoff of(String specification) { return BackoffSpec.parse(specification).build(); } + /** + * Returns an {@link ExponentialBackoffBuilder} that provides methods to configure + * backoff delay which is exponentially-increasing. The default values are as follows: + *
    + *
  • {@code initialDelayMillis}: {@value ExponentialBackoffBuilder#DEFAULT_INITIAL_DELAY_MILLIS}
  • + *
  • {@code maxDelayMillis}: {@value ExponentialBackoffBuilder#DEFAULT_MAX_DELAY_MILLIS}
  • + *
  • {@code multiplier}: {@value ExponentialBackoffBuilder#DEFAULT_MULTIPLIER}
  • + *
+ */ + @UnstableApi + static ExponentialBackoffBuilder builderForExponential() { + return new ExponentialBackoffBuilder(); + } + + /** + * Returns a {@link FibonacciBackoffBuilder} that provides methods to configure + * backoff delay which follows fibonacci sequence. + * f(n) = f(n-1) + f(n-2) where f(0) = f(1) = {@code initialDelayMillis} + * The default values are as follows: + *
    + *
  • {@code initialDelayMillis}: {@value FibonacciBackoffBuilder#DEFAULT_INITIAL_DELAY_MILLIS}
  • + *
  • {@code maxDelayMillis}: {@value FibonacciBackoffBuilder#DEFAULT_MAX_DELAY_MILLIS}
  • + *
+ */ + @UnstableApi + static FibonacciBackoffBuilder builderForFibonacci() { + return new FibonacciBackoffBuilder(); + } + + /** + * Returns a {@link FixedBackoffBuilder} that provides methods to configure + * backoff delay which is a fixed value. The default values are as follows: + *
    + *
  • {@code delayMillis}: {@value FixedBackoffBuilder#DEFAULT_DELAY_MILLIS}
  • + *
+ */ + @UnstableApi + static FixedBackoffBuilder builderForFixed() { + return new FixedBackoffBuilder(); + } + + /** + * Returns a {@link RandomBackoffBuilder} that provides methods to configure + * backoff delay which is a random value. The default values are as follows: + *
    + *
  • {@code minDelayMillis}: {@value RandomBackoffBuilder#DEFAULT_MIN_DELAY_MILLIS}
  • + *
  • {@code maxDelayMillis}: {@value RandomBackoffBuilder#DEFAULT_MAX_DELAY_MILLIS}
  • + *
+ */ + @UnstableApi + static RandomBackoffBuilder builderForRandom() { + return new RandomBackoffBuilder(); + } + /** * Returns the number of milliseconds to wait for before attempting a retry. * diff --git a/core/src/main/java/com/linecorp/armeria/client/retry/ExponentialBackoffBuilder.java b/core/src/main/java/com/linecorp/armeria/client/retry/ExponentialBackoffBuilder.java new file mode 100644 index 00000000000..5d1b4d1469e --- /dev/null +++ b/core/src/main/java/com/linecorp/armeria/client/retry/ExponentialBackoffBuilder.java @@ -0,0 +1,103 @@ +/* + * Copyright 2024 LY Corporation + * + * LY Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.armeria.client.retry; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.linecorp.armeria.common.annotation.UnstableApi; + +/** + * A builder for creating instances of Exponential {@link Backoff}. + * + *

This builder allows you to configure an exponential backoff strategy by specifying + * the initial delay, the maximum delay, and a multiplier. The exponential backoff + * increases the delay between retries exponentially, starting from the initial delay and + * multiplying the delay by the specified multiplier after each retry, up to the maximum delay.

+ * + *

Example usage:

+ * + *
+ * {@code
+ * Backoff backoff = Backoff.builderForExponential()
+ *     .initialDelayMillis(200)
+ *     .maxDelayMillis(10000)
+ *     .multiplier(2.0)
+ *     .build();
+ * }
+ * 
+ */ +@UnstableApi +public final class ExponentialBackoffBuilder extends AbstractBackoffBuilder { + + static final long DEFAULT_INITIAL_DELAY_MILLIS = 200; + static final long DEFAULT_MAX_DELAY_MILLIS = 10000; + static final double DEFAULT_MULTIPLIER = 2.0; + + private long initialDelayMillis = DEFAULT_INITIAL_DELAY_MILLIS; + private long maxDelayMillis = DEFAULT_MAX_DELAY_MILLIS; + private double multiplier = DEFAULT_MULTIPLIER; + + ExponentialBackoffBuilder() {} + + /** + * Sets the initial delay in milliseconds for the Exponential {@link Backoff}. + * + *

The initial delay is the starting value for the exponential backoff, determining + * the delay before the first retry. Subsequent delays will increase exponentially + * based on the multiplier.

+ * + * @param initialDelayMillis the initial delay in milliseconds + */ + public ExponentialBackoffBuilder initialDelayMillis(long initialDelayMillis) { + checkArgument(initialDelayMillis >= 0, "initialDelayMillis: %s (expected: >= 0)", initialDelayMillis); + this.initialDelayMillis = initialDelayMillis; + return this; + } + + /** + * Sets the maximum delay in milliseconds for the Exponential {@link Backoff}. + * + *

The maximum delay is the upper limit for the backoff delay. Once the delay reaches + * this value, it will not increase further, even if the multiplier would result in a higher value.

+ * + * @param maxDelayMillis the maximum delay in milliseconds + */ + public ExponentialBackoffBuilder maxDelayMillis(long maxDelayMillis) { + checkArgument(maxDelayMillis >= 0, "maxDelayMillis: %s (expected: >= 0)", maxDelayMillis); + this.maxDelayMillis = maxDelayMillis; + return this; + } + + /** + * Sets the multiplier for the Exponential {@link Backoff}. + * + *

The multiplier controls how much the delay increases after each retry. + * The delay for each retry is determined by multiplying the previous delay by this value, + * until the maximum delay is reached.

+ * + * @param multiplier the multiplier for the exponential backoff + */ + public ExponentialBackoffBuilder multiplier(double multiplier) { + checkArgument(multiplier > 1.0, "multiplier: %s (expected: > 1.0)", multiplier); + this.multiplier = multiplier; + return this; + } + + @Override + Backoff doBuild() { + return new ExponentialBackoff(initialDelayMillis, maxDelayMillis, multiplier); + } +} diff --git a/core/src/main/java/com/linecorp/armeria/client/retry/FibonacciBackoffBuilder.java b/core/src/main/java/com/linecorp/armeria/client/retry/FibonacciBackoffBuilder.java new file mode 100644 index 00000000000..7fa0feaf1fa --- /dev/null +++ b/core/src/main/java/com/linecorp/armeria/client/retry/FibonacciBackoffBuilder.java @@ -0,0 +1,87 @@ +/* + * Copyright 2024 LY Corporation + * + * LY Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.armeria.client.retry; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.linecorp.armeria.common.annotation.UnstableApi; + +/** + * A builder for creating instances of Fibonacci {@link Backoff}. + * + *

This builder allows you to configure a Fibonacci backoff strategy by specifying + * an initial delay and a maximum delay in milliseconds. The Fibonacci backoff strategy + * increases the delay between retries according to the Fibonacci sequence, while respecting + * the configured maximum delay.

+ * + *

Example usage:

+ * + *
+ * {@code
+ * Backoff backoff = Backoff.builderForFibonacci()
+ *     .initialDelayMillis(200)
+ *     .maxDelayMillis(10000)
+ *     .build();
+ * }
+ * 
+ */ +@UnstableApi +public final class FibonacciBackoffBuilder extends AbstractBackoffBuilder { + + static final long DEFAULT_INITIAL_DELAY_MILLIS = 200; + static final long DEFAULT_MAX_DELAY_MILLIS = 10000; + + private long initialDelayMillis = 200; + private long maxDelayMillis = 10000; + + FibonacciBackoffBuilder() {} + + /** + * Sets the initial delay in milliseconds for the Fibonacci {@link Backoff}. + * + *

The initial delay is the base value from which the Fibonacci sequence will start, + * and it determines the delay before the first retry.

+ * + * @param initialDelayMillis the initial delay in milliseconds + */ + public FibonacciBackoffBuilder initialDelayMillis(long initialDelayMillis) { + checkArgument(initialDelayMillis >= 0, + "initialDelayMillis: %s (expected: >= 0)", initialDelayMillis); + + this.initialDelayMillis = initialDelayMillis; + return this; + } + + /** + * Sets the maximum delay in milliseconds for the Fibonacci {@link Backoff}. + * + *

The maximum delay sets an upper limit to the delays generated by the Fibonacci + * sequence. Once the delays reach this value, they will not increase further.

+ * + * @param maxDelayMillis the maximum delay in milliseconds + */ + public FibonacciBackoffBuilder maxDelayMillis(long maxDelayMillis) { + checkArgument(maxDelayMillis >= 0, + "maxDelayMillis: %s (expected: >= 0)", maxDelayMillis); + this.maxDelayMillis = maxDelayMillis; + return this; + } + + @Override + Backoff doBuild() { + return new FibonacciBackoff(initialDelayMillis, maxDelayMillis); + } +} diff --git a/core/src/main/java/com/linecorp/armeria/client/retry/FixedBackoffBuilder.java b/core/src/main/java/com/linecorp/armeria/client/retry/FixedBackoffBuilder.java new file mode 100644 index 00000000000..4893fc85b10 --- /dev/null +++ b/core/src/main/java/com/linecorp/armeria/client/retry/FixedBackoffBuilder.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024 LY Corporation + * + * LY Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.armeria.client.retry; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.linecorp.armeria.common.annotation.UnstableApi; + +/** + * A builder for creating instances of fixed {@link Backoff}. + * + *

This builder allows you to configure the delay duration for a fixed backoff strategy. + * You can specify the delay in milliseconds and then create a Fixed {@link Backoff} instance + * with the configured delay.

+ * + *

Example usage:

+ * + *
+ * {@code
+ * Backoff backoff = Backoff.builderForFixed()
+ *     .delayMillis(500)
+ *     .build();
+ * }
+ * 
+ */ +@UnstableApi +public final class FixedBackoffBuilder extends AbstractBackoffBuilder { + static final long DEFAULT_DELAY_MILLIS = 500; + + private long delayMillis = 500; + + FixedBackoffBuilder() {} + + /** + * Sets the delay duration in milliseconds for the Fixed {@link Backoff}. + * + *

This value determines the fixed amount of time the backoff will delay + * before retrying an operation.

+ * + * @param delayMillis the delay in milliseconds + */ + public FixedBackoffBuilder delayMillis(long delayMillis) { + checkArgument(delayMillis >= 0, "delayMillis: %s (expected: >= 0)", delayMillis); + this.delayMillis = delayMillis; + return this; + } + + @Override + Backoff doBuild() { + return new FixedBackoff(delayMillis); + } +} diff --git a/core/src/main/java/com/linecorp/armeria/client/retry/RandomBackoffBuilder.java b/core/src/main/java/com/linecorp/armeria/client/retry/RandomBackoffBuilder.java new file mode 100644 index 00000000000..404b2482812 --- /dev/null +++ b/core/src/main/java/com/linecorp/armeria/client/retry/RandomBackoffBuilder.java @@ -0,0 +1,101 @@ +/* + * Copyright 2024 LY Corporation + * + * LY Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.armeria.client.retry; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +import java.util.Random; +import java.util.function.Supplier; + +import com.linecorp.armeria.common.annotation.UnstableApi; + +/** + * A builder for creating instances of Random {@link Backoff}. + * + *

This builder allows you to configure a random backoff strategy by specifying + * a minimum delay, maximum delay in milliseconds and random supplier. + *

Example usage:

+ * + *
+ * {@code
+ * BackOff backoff = Backoff.builderForRandom()
+ *     .minDelayMillis(200)
+ *     .maxDelayMillis(10000)
+ *     .build();
+ * }
+ * 
+ */ +@UnstableApi +public final class RandomBackoffBuilder extends AbstractBackoffBuilder { + + static final long DEFAULT_MIN_DELAY_MILLIS = 200; + static final long DEFAULT_MAX_DELAY_MILLIS = 10000; + + private long minDelayMillis = 200; + private long maxDelayMillis = 10000; + private Supplier randomSupplier = Random::new; + + RandomBackoffBuilder() {} + + /** + * Sets the minimum delay, in milliseconds, for the Random {@link Backoff}. + * + *

This value represents the minimum time the backoff will delay before retrying an operation.

+ * + * @param minDelayMillis the minimum delay in milliseconds + * @return this {@code RandomBackoffBuilder} instance for method chaining + */ + public RandomBackoffBuilder minDelayMillis(long minDelayMillis) { + checkArgument(minDelayMillis >= 0, "minDelayMillis: %s (expected: >= 0)", minDelayMillis); + this.minDelayMillis = minDelayMillis; + return this; + } + + /** + * Sets the maximum delay, in milliseconds, for the Random {@link Backoff}. + * + *

This value represents the maximum time the backoff will delay before retrying an operation.

+ * + * @param maxDelayMillis the maximum delay in milliseconds + * @return this {@code RandomBackoffBuilder} instance for method chaining + */ + public RandomBackoffBuilder maxDelayMillis(long maxDelayMillis) { + checkArgument(maxDelayMillis >= 0, "maxDelayMillis: %s (expected: >= 0)", maxDelayMillis); + this.maxDelayMillis = maxDelayMillis; + return this; + } + + /** + * Sets the {@link Supplier} that provides instances of {@link Random} for the Random {@link Backoff}. + * + *

This supplier will be used to generate random values when determining the + * backoff delay between retries.

+ * + * @param randomSupplier a {@link Supplier} that provides {@link Random} instances + * @return this {@code RandomBackoffBuilder} instance for method chaining + */ + public RandomBackoffBuilder randomSupplier(Supplier randomSupplier) { + requireNonNull(randomSupplier, "randomSupplier"); + this.randomSupplier = randomSupplier; + return this; + } + + @Override + Backoff doBuild() { + return new RandomBackoff(minDelayMillis, maxDelayMillis, randomSupplier); + } +} diff --git a/core/src/test/java/com/linecorp/armeria/client/retry/BackoffTest.java b/core/src/test/java/com/linecorp/armeria/client/retry/BackoffTest.java index 93b6ad1cf59..7c15a5d92fc 100644 --- a/core/src/test/java/com/linecorp/armeria/client/retry/BackoffTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/retry/BackoffTest.java @@ -74,6 +74,18 @@ void withJitter() throws Exception { assertThat(backoff.nextDelayMillis(3)).isEqualTo(803); } + @Test + void withJitterBuilder() throws Exception { + final Random random = new Random(1); + final Backoff backoff = Backoff.builderForFixed() + .delayMillis(1000) + .jitter(-0.3, 0.3, () -> random) + .build(); + assertThat(backoff.nextDelayMillis(1)).isEqualTo(1240); + assertThat(backoff.nextDelayMillis(2)).isEqualTo(771); + assertThat(backoff.nextDelayMillis(3)).isEqualTo(803); + } + @Test void withMaxAttempts() throws Exception { final Backoff backoff = Backoff.fixed(100).withMaxAttempts(2); @@ -82,6 +94,17 @@ void withMaxAttempts() throws Exception { assertThat(backoff.nextDelayMillis(3)).isEqualTo(-1); } + @Test + void withMaxAttemptsBuilder() throws Exception { + final Backoff backoff = Backoff.builderForFixed() + .delayMillis(100) + .maxAttempts(2) + .build(); + assertThat(backoff.nextDelayMillis(1)).isEqualTo(100); + assertThat(backoff.nextDelayMillis(2)).isEqualTo(-1); + assertThat(backoff.nextDelayMillis(3)).isEqualTo(-1); + } + @Test void unwrap() { final Backoff backoff = Backoff.fixed(100); diff --git a/core/src/test/java/com/linecorp/armeria/client/retry/ExponentialBackoffTest.java b/core/src/test/java/com/linecorp/armeria/client/retry/ExponentialBackoffTest.java index 4eed997b99d..c9bb116aa10 100644 --- a/core/src/test/java/com/linecorp/armeria/client/retry/ExponentialBackoffTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/retry/ExponentialBackoffTest.java @@ -31,6 +31,19 @@ void normal() { assertThat(backoff.nextDelayMillis(5)).isEqualTo(120); assertThat(backoff.nextDelayMillis(6)).isEqualTo(120); assertThat(backoff.nextDelayMillis(7)).isEqualTo(120); + + final Backoff backoff2 = Backoff.builderForExponential() + .initialDelayMillis(10) + .maxDelayMillis(120) + .multiplier(3.0) + .build(); + assertThat(backoff2.nextDelayMillis(1)).isEqualTo(10); + assertThat(backoff2.nextDelayMillis(2)).isEqualTo(30); + assertThat(backoff2.nextDelayMillis(3)).isEqualTo(90); + assertThat(backoff2.nextDelayMillis(4)).isEqualTo(120); + assertThat(backoff2.nextDelayMillis(5)).isEqualTo(120); + assertThat(backoff2.nextDelayMillis(6)).isEqualTo(120); + assertThat(backoff2.nextDelayMillis(7)).isEqualTo(120); } @Test @@ -42,6 +55,17 @@ void nonPrecomputed() { assertThat(backoff.nextDelayMillis(30)).isEqualTo(5368709120L); // Not precomputed, should fallback to computation and return a correct value. assertThat(backoff.nextDelayMillis(31)).isEqualTo(10737418240L); + + final Backoff backoff2 = Backoff.builderForExponential() + .initialDelayMillis(10) + .maxDelayMillis(Long.MAX_VALUE) + .multiplier(2.0) + .build(); + assertThat(backoff2.nextDelayMillis(1)).isEqualTo(10); + assertThat(backoff2.nextDelayMillis(2)).isEqualTo(20); + assertThat(backoff2.nextDelayMillis(3)).isEqualTo(40); + assertThat(backoff2.nextDelayMillis(30)).isEqualTo(5368709120L); + assertThat(backoff2.nextDelayMillis(31)).isEqualTo(10737418240L); } @Test @@ -51,5 +75,15 @@ void overflow() { assertThat(backoff.nextDelayMillis(2)).isEqualTo((long) (Long.MAX_VALUE / 3 * 2.0)); assertThat(backoff.nextDelayMillis(3)).isEqualTo(Long.MAX_VALUE); assertThat(backoff.nextDelayMillis(4)).isEqualTo(Long.MAX_VALUE); + + final Backoff backoff2 = Backoff.builderForExponential() + .initialDelayMillis(Long.MAX_VALUE / 3) + .maxDelayMillis(Long.MAX_VALUE) + .multiplier(2.0) + .build(); + assertThat(backoff2.nextDelayMillis(1)).isEqualTo(Long.MAX_VALUE / 3); + assertThat(backoff2.nextDelayMillis(2)).isEqualTo((long) (Long.MAX_VALUE / 3 * 2.0)); + assertThat(backoff2.nextDelayMillis(3)).isEqualTo(Long.MAX_VALUE); + assertThat(backoff2.nextDelayMillis(4)).isEqualTo(Long.MAX_VALUE); } } diff --git a/core/src/test/java/com/linecorp/armeria/client/retry/FibonacciBackoffTest.java b/core/src/test/java/com/linecorp/armeria/client/retry/FibonacciBackoffTest.java index 8a91981d990..1e9ae000061 100644 --- a/core/src/test/java/com/linecorp/armeria/client/retry/FibonacciBackoffTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/retry/FibonacciBackoffTest.java @@ -30,6 +30,16 @@ void testNextDelay() { assertThat(backoff.nextDelayMillis(3)).isEqualTo(20); assertThat(backoff.nextDelayMillis(4)).isEqualTo(30); assertThat(backoff.nextDelayMillis(7)).isEqualTo(120); + + final Backoff backoff2 = Backoff.builderForFibonacci() + .initialDelayMillis(10) + .maxDelayMillis(120) + .build(); + assertThat(backoff2.nextDelayMillis(1)).isEqualTo(10); + assertThat(backoff2.nextDelayMillis(2)).isEqualTo(10); + assertThat(backoff2.nextDelayMillis(3)).isEqualTo(20); + assertThat(backoff2.nextDelayMillis(4)).isEqualTo(30); + assertThat(backoff2.nextDelayMillis(7)).isEqualTo(120); } @Test @@ -38,6 +48,14 @@ void testLargeNumberOfRetries() { assertThat(backoff.nextDelayMillis(30)).isEqualTo(832040); assertThat(backoff.nextDelayMillis(31)).isEqualTo(1346269); assertThat(backoff.nextDelayMillis(32)).isEqualTo(2178309); + + final Backoff backoff2 = Backoff.builderForFibonacci() + .initialDelayMillis(1) + .maxDelayMillis(Long.MAX_VALUE) + .build(); + assertThat(backoff2.nextDelayMillis(30)).isEqualTo(832040); + assertThat(backoff2.nextDelayMillis(31)).isEqualTo(1346269); + assertThat(backoff2.nextDelayMillis(32)).isEqualTo(2178309); } @Test @@ -48,17 +66,37 @@ void testOverflow() { assertThat(backoff.nextDelayMillis(3)).isEqualTo(Long.MAX_VALUE / 3 * 2); assertThat(backoff.nextDelayMillis(4)).isEqualTo(Long.MAX_VALUE / 3 * 3); assertThat(backoff.nextDelayMillis(5)).isEqualTo(Long.MAX_VALUE); + + final Backoff backoff2 = Backoff.builderForFibonacci() + .initialDelayMillis(Long.MAX_VALUE / 3) + .maxDelayMillis(Long.MAX_VALUE) + .build(); + assertThat(backoff.nextDelayMillis(1)).isEqualTo(Long.MAX_VALUE / 3); + assertThat(backoff.nextDelayMillis(2)).isEqualTo(Long.MAX_VALUE / 3); + assertThat(backoff.nextDelayMillis(3)).isEqualTo(Long.MAX_VALUE / 3 * 2); + assertThat(backoff.nextDelayMillis(4)).isEqualTo(Long.MAX_VALUE / 3 * 3); + assertThat(backoff.nextDelayMillis(5)).isEqualTo(Long.MAX_VALUE); } @Test void testConstraintInitialDelay() { assertThatThrownBy(() -> new FibonacciBackoff(-5, 120)) .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> Backoff.builderForFibonacci() + .initialDelayMillis(-5) + .maxDelayMillis(120) + .build()) + .isInstanceOf(IllegalArgumentException.class); } @Test void testConstraintMaxDelay() { assertThatThrownBy(() -> new FibonacciBackoff(10, 0)) .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> Backoff.builderForFibonacci() + .initialDelayMillis(10) + .maxDelayMillis(0) + .build()) + .isInstanceOf(IllegalArgumentException.class); } } diff --git a/core/src/test/java/com/linecorp/armeria/client/retry/RandomBackoffTest.java b/core/src/test/java/com/linecorp/armeria/client/retry/RandomBackoffTest.java index 13a72cba6bb..cbe212b1b6c 100644 --- a/core/src/test/java/com/linecorp/armeria/client/retry/RandomBackoffTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/retry/RandomBackoffTest.java @@ -33,6 +33,20 @@ public void nextDelayMillis() throws Exception { assertThat(backoff.nextDelayMillis(1)).isEqualTo(95); } + @Test + public void nextDelayMillisWithBuilder() throws Exception { + final Random r = new Random(1); + final Backoff backoff2 = Backoff.builderForRandom() + .minDelayMillis(10) + .maxDelayMillis(100) + .randomSupplier(() -> r) + .build(); + assertThat(backoff2.nextDelayMillis(1)).isEqualTo(18); + assertThat(backoff2.nextDelayMillis(1)).isEqualTo(93); + assertThat(backoff2.nextDelayMillis(1)).isEqualTo(12); + assertThat(backoff2.nextDelayMillis(1)).isEqualTo(95); + } + @Test public void validation() { // Negative minDelayMillis