Skip to content

Commit

Permalink
Add builder for Backoff (#5488)
Browse files Browse the repository at this point in the history
Motivation:

- #5483

Modifications:

- Add `Builder` class for various backoff implementations.

Result:

- Closes #5483
- Will be able to use the builder class to create instances of BackOff.
  • Loading branch information
kth496 authored Feb 21, 2025
1 parent 3b27340 commit 98e3054
Show file tree
Hide file tree
Showing 10 changed files with 612 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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<SELF extends AbstractBackoffBuilder<SELF>> {
@Nullable
private Double minJitterRate;
@Nullable
private Double maxJitterRate;
@Nullable
private Integer maxAttempts;
@Nullable
private Supplier<Random> 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<Random> 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<Random> 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;
}
}
55 changes: 55 additions & 0 deletions core/src/main/java/com/linecorp/armeria/client/retry/Backoff.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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:
* <ul>
* <li>{@code initialDelayMillis}: {@value ExponentialBackoffBuilder#DEFAULT_INITIAL_DELAY_MILLIS}</li>
* <li>{@code maxDelayMillis}: {@value ExponentialBackoffBuilder#DEFAULT_MAX_DELAY_MILLIS}</li>
* <li>{@code multiplier}: {@value ExponentialBackoffBuilder#DEFAULT_MULTIPLIER}</li>
* </ul>
*/
@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:
* <ul>
* <li>{@code initialDelayMillis}: {@value FibonacciBackoffBuilder#DEFAULT_INITIAL_DELAY_MILLIS}</li>
* <li>{@code maxDelayMillis}: {@value FibonacciBackoffBuilder#DEFAULT_MAX_DELAY_MILLIS}</li>
* </ul>
*/
@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:
* <ul>
* <li>{@code delayMillis}: {@value FixedBackoffBuilder#DEFAULT_DELAY_MILLIS}</li>
* </ul>
*/
@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:
* <ul>
* <li>{@code minDelayMillis}: {@value RandomBackoffBuilder#DEFAULT_MIN_DELAY_MILLIS}</li>
* <li>{@code maxDelayMillis}: {@value RandomBackoffBuilder#DEFAULT_MAX_DELAY_MILLIS}</li>
* </ul>
*/
@UnstableApi
static RandomBackoffBuilder builderForRandom() {
return new RandomBackoffBuilder();
}

/**
* Returns the number of milliseconds to wait for before attempting a retry.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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}.
*
* <p>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.</p>
*
* <p>Example usage:</p>
*
* <pre>
* {@code
* Backoff backoff = Backoff.builderForExponential()
* .initialDelayMillis(200)
* .maxDelayMillis(10000)
* .multiplier(2.0)
* .build();
* }
* </pre>
*/
@UnstableApi
public final class ExponentialBackoffBuilder extends AbstractBackoffBuilder<ExponentialBackoffBuilder> {

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}.
*
* <p>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.</p>
*
* @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}.
*
* <p>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.</p>
*
* @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}.
*
* <p>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.</p>
*
* @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);
}
}
Original file line number Diff line number Diff line change
@@ -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}.
*
* <p>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.</p>
*
* <p>Example usage:</p>
*
* <pre>
* {@code
* Backoff backoff = Backoff.builderForFibonacci()
* .initialDelayMillis(200)
* .maxDelayMillis(10000)
* .build();
* }
* </pre>
*/
@UnstableApi
public final class FibonacciBackoffBuilder extends AbstractBackoffBuilder<FibonacciBackoffBuilder> {

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}.
*
* <p>The initial delay is the base value from which the Fibonacci sequence will start,
* and it determines the delay before the first retry.</p>
*
* @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}.
*
* <p>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.</p>
*
* @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);
}
}
Loading

0 comments on commit 98e3054

Please sign in to comment.