Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add builder for Backoff #5488

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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;

/**
* A skeletal builder implementation for {@link Backoff}.
*/
public abstract class AbstractBackoffBuilder<SELF extends AbstractBackoffBuilder<SELF>> {
private double minJitterRate;
private double maxJitterRate;
private Supplier<Random> randomSupplier = ThreadLocalRandom::current;
private int maxAttempts;

@SuppressWarnings("unchecked")
final SELF self() {
return (SELF) this;
}

/**
* Sets the minimum and maximum jitter rates to apply to the delay.
*/
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.
*/
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
* .
*/
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 (maxJitterRate > minJitterRate) {
backoff = new JitterAddingBackoff(backoff, minJitterRate, maxJitterRate, randomSupplier);
}
if (maxAttempts > 0) {
backoff = new AttemptLimitingBackoff(backoff, maxAttempts);
}
return backoff;
}
Comment on lines +68 to +82
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After much thought, I came up with the above solution. Is there a better way?

}
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,39 @@ static Backoff of(String specification) {
return BackoffSpec.parse(specification).build();
}

/**
* Returns a {@link ExponentialBackoffBuilder} that provides methods to configure
* backoff delay which is exponentially-increasing.
*/
static ExponentialBackoffBuilder builderForExponential() {
return new ExponentialBackoffBuilder();
}

/**
* Returns a {@link FixedBackoffBuilder} 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}
*/
static FibonacciBackoffBuilder builderForFibonacci() {
return new FibonacciBackoffBuilder();
}

/**
* Returns a {@link FixedBackoffBuilder} that provides methods to configure
* backoff delay which is a fixed value.
*/
static FixedBackoffBuilder builderForFixed() {
return new FixedBackoffBuilder();
}

/**
* Returns a {@link RandomBackoffBuilder} that provides methods to configure
* backoff delay which is a random value.
*/
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,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 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
* ExponentialBackoff backoff = Backoff.builderForExponential()
* .initialDelayMillis(100)
* .maxDelayMillis(10000)
* .multiplier(2.0)
* .build();
* }
* </pre>
*/
@UnstableApi
public final class ExponentialBackoffBuilder extends AbstractBackoffBuilder<ExponentialBackoffBuilder> {
private long initialDelayMillis;
private long maxDelayMillis;
private double multiplier = 2.0;

ikhoon marked this conversation as resolved.
Show resolved Hide resolved
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
* @return this {@code ExponentialBackoffBuilder} instance for method chaining
*/
public ExponentialBackoffBuilder initialDelayMillis(long initialDelayMillis) {
checkArgument(initialDelayMillis >= 0, "initialDelayMillis: %s (expected: >= 0)", initialDelayMillis);
this.initialDelayMillis = initialDelayMillis;
ikhoon marked this conversation as resolved.
Show resolved Hide resolved
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
* @return this {@code ExponentialBackoffBuilder} instance for method chaining
*/
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
* @return this {@code ExponentialBackoffBuilder} instance for method chaining
*/
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,85 @@
/*
* 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
* FibonacciBackoff backoff = Backoff.builderForFibonacci()
* .initialDelayMillis(100)
* .maxDelayMillis(10000)
* .build();
* }
* </pre>
*/
@UnstableApi
public final class FibonacciBackoffBuilder extends AbstractBackoffBuilder<FibonacciBackoffBuilder> {
private long initialDelayMillis;
private long maxDelayMillis;

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
* @return this {@code FibonacciBackoffBuilder} instance for method chaining
*/
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
* @return this {@code FibonacciBackoffBuilder} instance for method chaining
*/
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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}.
*
* <p>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.</p>
*
* <p>Example usage:</p>
*
* <pre>
* {@code
* FixedBackoff backoff = Backoff.builderForFixed()
* .delayMillis(1000)
* .build();
* }
* </pre>
*/
@UnstableApi
public final class FixedBackoffBuilder extends AbstractBackoffBuilder<FixedBackoffBuilder> {
private long delayMillis;

FixedBackoffBuilder() {}

/**
* Sets the delay duration in milliseconds for the Fixed {@link Backoff}.
*
* <p>This value determines the fixed amount of time the backoff will delay
* before retrying an operation.</p>
*
* @param delayMillis the delay in milliseconds
* @return this {@code FixedBackoffBuilder} instance for method chaining
*/
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);
}
}
Loading
Loading