FixedDelayRetryStrategy.java
/*
* okhttp-client-extensions - A set of helpful extensions to support okhttp clients
* Copyright © 2025 Andy Miles (andy.miles@amilesend.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.amilesend.client.connection.retry;
import com.amilesend.client.connection.ResponseException;
import com.amilesend.client.connection.ThrottledException;
import lombok.Builder;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* A fixed-delay retry strategy.
*
* @see RetryStrategy
*/
@Builder
@Slf4j
public class FixedDelayRetryStrategy implements RetryStrategy {
/** The maximum number of attempts to make. Default: 3 */
@Builder.Default
private final int maxAttempts = 3;
/** The delay in milliseconds to make between invocations. */
@Builder.Default
private final long delayMs = 500L;
/** The maximum amount of jitter in milliseconds to apply per retry. Default: 100 */
@Builder.Default
private final long maxJitterMs = 100L;
@Override
public RetriableCallResponse invoke(@NonNull final Retriable retriable) {
int attempts = 0;
final List<Exception> exceptions = new ArrayList<>(maxAttempts);
do {
try {
++attempts;
final Response response = retriable.call();
validateResponseCode(response);
return RetriableCallResponse.builder()
.response(response)
.exceptions(exceptions)
.attempts(attempts)
.build();
} catch (final IOException | ThrottledException | ResponseException ex) {
exceptions.add(ex);
if (attempts >= maxAttempts) {
return RetriableCallResponse.builder()
.attempts(attempts)
.exceptions(exceptions)
.build();
}
try {
final long delay = calculateDelay(ex);
log.debug("Delaying next retry by {} ms", delay);
Thread.sleep(delay);
} catch (final InterruptedException iex) {
exceptions.add(ex);
Thread.currentThread().interrupt();
return RetriableCallResponse.builder()
.attempts(attempts)
.exceptions(exceptions)
.build();
}
} catch (final Exception ex) {
exceptions.add(ex);
return RetriableCallResponse.builder()
.attempts(attempts)
.exceptions(exceptions)
.build();
}
} while (true);
}
protected long calculateDelay(final Exception thrown) {
final long jitter = (long)(Math.random() * maxJitterMs);
if (ThrottledException.class.isInstance(thrown)) {
return ((ThrottledException) thrown).getRetryAfterSeconds() * 1000L + jitter;
}
return delayMs + jitter;
}
}