All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.davidmoten.aws.lw.client.internal.Retries Maven / Gradle / Ivy

Go to download

Lightweight client for all AWS services (but still with useful builders and XML parser)

The newest version!
package com.github.davidmoten.aws.lw.client.internal;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.concurrent.Callable;
import java.util.function.Predicate;

import com.github.davidmoten.aws.lw.client.MaxAttemptsExceededException;
import com.github.davidmoten.aws.lw.client.internal.util.Preconditions;

public final class Retries {

    private final long initialIntervalMs;
    private final int maxAttempts;
    private final double backoffFactor;
    private final long maxIntervalMs;
    private final double jitter;
    private final Predicate valueShouldRetry;
    private final Predicate throwableShouldRetry;

    public Retries(long initialIntervalMs, int maxAttempts, double backoffFactor, double jitter, long maxIntervalMs,
            Predicate valueShouldRetry, Predicate throwableShouldRetry) {
        Preconditions.checkArgument(jitter >= 0 && jitter <= 1, "jitter must be between 0 and 1 inclusive");
        this.initialIntervalMs = initialIntervalMs;
        this.maxAttempts = maxAttempts;
        this.backoffFactor = backoffFactor;
        this.jitter = jitter;
        this.maxIntervalMs = maxIntervalMs;
        this.valueShouldRetry = valueShouldRetry;
        this.throwableShouldRetry = throwableShouldRetry;
    }

    public static  Retries create(Predicate valueShouldRetry,
            Predicate throwableShouldRetry) {
        return new Retries( //
                100, //
                4, //
                2.0, //
                0.0, // no jitter
                20000, //
                valueShouldRetry, //
                throwableShouldRetry);
    }

    public T call(Callable callable) {
        return call(callable, valueShouldRetry);
    }

    public  S call(Callable callable, Predicate valueShouldRetry) {
        long intervalMs = initialIntervalMs;
        int attempt = 0;
        while (true) {
            S value;
            try {
                attempt++;
                value = callable.call();
                if (!valueShouldRetry.test(value)) {
                    return value;
                }
                if (reachedMaxAttempts(attempt, maxAttempts)) {
                    // note that caller is not aware that maxAttempts were reached, the caller just
                    // receives the last error response
                    return value;
                }
            } catch (Throwable t) {
                if (!throwableShouldRetry.test(t)) {
                    rethrow(t);
                }
                if (reachedMaxAttempts(attempt, maxAttempts)) {
                    throw new MaxAttemptsExceededException("exceeded max attempts " + maxAttempts, t);
                }
            }
            sleep(intervalMs);
            //calculate the interval for the next retry
            intervalMs = Math.round(backoffFactor * intervalMs);
            if (maxIntervalMs > 0) {
                intervalMs = Math.min(maxIntervalMs, intervalMs);
            }
            // apply jitter (if 0 then no change)
            intervalMs = Math.round((1 - jitter * Math.random()) * intervalMs);
        }
    }

    // VisibleForTesting
    static boolean reachedMaxAttempts(int attempt, int maxAttempts) {
        return maxAttempts > 0 && attempt >= maxAttempts;
    }

    static void sleep(long intervalMs) {
        try {
            Thread.sleep(intervalMs);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public  Retries withValueShouldRetry(Predicate valueShouldRetry) {
        return new Retries(initialIntervalMs, maxAttempts, backoffFactor, jitter, maxIntervalMs, valueShouldRetry,
                throwableShouldRetry);
    }

    public Retries withInitialIntervalMs(long initialIntervalMs) {
        return new Retries(initialIntervalMs, maxAttempts, backoffFactor, jitter, maxIntervalMs, valueShouldRetry,
                throwableShouldRetry);
    }

    public Retries withMaxAttempts(int maxAttempts) {
        return new Retries(initialIntervalMs, maxAttempts, backoffFactor, jitter, maxIntervalMs, valueShouldRetry,
                throwableShouldRetry);
    }

    public Retries withBackoffFactor(double backoffFactor) {
        return new Retries(initialIntervalMs, maxAttempts, backoffFactor, jitter, maxIntervalMs, valueShouldRetry,
                throwableShouldRetry);
    }

    public Retries withMaxIntervalMs(long maxIntervalMs) {
        return new Retries(initialIntervalMs, maxAttempts, backoffFactor, jitter, maxIntervalMs, valueShouldRetry,
                throwableShouldRetry);
    }
    
    public Retries withJitter(double jitter) {
        return new Retries(initialIntervalMs, maxAttempts, backoffFactor, jitter, maxIntervalMs, valueShouldRetry,
                throwableShouldRetry);
    }

    public Retries withThrowableShouldRetry(Predicate throwableShouldRetry) {
        return new Retries(initialIntervalMs, maxAttempts, backoffFactor, jitter, maxIntervalMs, valueShouldRetry,
                throwableShouldRetry);
    }

    public Retries copy() {
        return new Retries<>(initialIntervalMs, maxAttempts, backoffFactor, jitter, maxIntervalMs, valueShouldRetry,
                throwableShouldRetry);
    }

    // VisibleForTesting
    static void rethrow(Throwable t) throws Error {
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else if (t instanceof Error) {
            throw (Error) t;
        } else if (t instanceof IOException) {
            throw new UncheckedIOException((IOException) t);
        } else {
            throw new RuntimeException(t);
        }
    }

}