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

io.tarantool.driver.api.retry.TarantoolRequestRetryPolicies Maven / Gradle / Ivy

package io.tarantool.driver.api.retry;

import io.tarantool.driver.exceptions.TarantoolAttemptsLimitException;
import io.tarantool.driver.exceptions.TarantoolClientException;
import io.tarantool.driver.exceptions.TarantoolConnectionException;
import io.tarantool.driver.exceptions.TarantoolInternalNetworkException;
import io.tarantool.driver.exceptions.TarantoolNoSuchProcedureException;
import io.tarantool.driver.exceptions.TarantoolTimeoutException;
import io.tarantool.driver.utils.Assert;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Class-container for built-in request retry policies
 *
 * @author Alexey Kuzin
 * @author Artyom Dubinin
 * @author Oleg Kuznetsov
 */
public final class TarantoolRequestRetryPolicies {

    public static final Predicate retryAll = t -> true;
    public static final Predicate retryNone = t -> false;
    public static final long DEFAULT_ONE_HOUR_TIMEOUT = TimeUnit.HOURS.toMillis(1); //ms

    /**
     * Check all known network exceptions
     *
     * @return predicate for checking all network exceptions
     */
    public static Predicate retryNetworkErrors() {
        return TarantoolRequestRetryPolicies::isNetworkError;
    }

    /**
     * Check {@link TarantoolNoSuchProcedureException}
     *
     * @return predicate for checking {@link TarantoolNoSuchProcedureException}
     */
    public static Predicate retryTarantoolNoSuchProcedureErrors() {
        return throwable -> throwable instanceof TarantoolNoSuchProcedureException;
    }

    private static boolean isNetworkError(Throwable e) {
        return e instanceof TimeoutException ||
            e instanceof TarantoolConnectionException ||
            e instanceof TarantoolInternalNetworkException;
    }

    private TarantoolRequestRetryPolicies() {
    }

    /**
     * Retry policy that performs unbounded number of attempts.
     * If the exception check passes, the policy returns {@code true}.
     *
     * @param  exception checking callback function type
     */
    public static final class InfiniteRetryPolicy> implements RequestRetryPolicy {

        private final long requestTimeout; //ms
        private final long operationTimeout; //ms
        private final long delay; //ms
        private final T exceptionCheck;

        /**
         * Basic constructor
         *
         * @param requestTimeout   timeout for one retry attempt, in milliseconds
         * @param operationTimeout timeout for the whole operation, in milliseconds
         * @param delay            delay between attempts, in milliseconds
         * @param exceptionCheck   predicate checking whether the given exception may be retried
         */
        public InfiniteRetryPolicy(long requestTimeout, long operationTimeout, long delay, T exceptionCheck) {
            Assert.state(requestTimeout >= 0, "Timeout must be greater or equal than 0!");
            Assert.state(operationTimeout >= requestTimeout,
                "Operation timeout must be greater or equal than requestTimeout!");
            Assert.state(delay >= 0, "Delay must be greater or equal than 0!");
            Assert.notNull(exceptionCheck, "Exception checking callback must not be null!");

            this.requestTimeout = requestTimeout;
            this.operationTimeout = operationTimeout;
            this.delay = delay;
            this.exceptionCheck = exceptionCheck;
        }

        @Override
        public boolean canRetryRequest(Throwable throwable) {
            if (testException(exceptionCheck, throwable)) {
                if (delay > 0) {
                    trySleep(delay);
                }
                return true;
            }
            return false;
        }

        @Override
        public long getRequestTimeout() {
            return requestTimeout;
        }

        public long getOperationTimeout() {
            return operationTimeout;
        }

        @Override
        public  CompletableFuture wrapOperation(Supplier> operation, Executor executor) {

            Assert.notNull(operation, "Operation must not be null");
            Assert.notNull(executor, "Executor must not be null");

            return CompletableFuture.supplyAsync(() -> {
                long timeElapsed = 0;
                long tStart;
                Throwable ex;
                do {
                    tStart = System.nanoTime();
                    try {
                        return operation.get().get(getRequestTimeout(), TimeUnit.MILLISECONDS);
                    } catch (TimeoutException | InterruptedException e) {
                        ex = e;
                    } catch (ExecutionException e) {
                        ex = e.getCause();
                    }
                    timeElapsed = timeElapsed + (System.nanoTime() - tStart) / 1_000_000L;
                    if (timeElapsed >= getOperationTimeout()) {
                        ex = new TarantoolTimeoutException(
                            timeElapsed,
                            ex);
                        break;
                    }
                } while (this.canRetryRequest(ex));
                throw new CompletionException(ex);
            }, executor);
        }
    }

    /**
     * Factory for {@link InfiniteRetryPolicy}
     *
     * @param  exception checking predicate type
     */
    public static final class InfiniteRetryPolicyFactory>
        implements RequestRetryPolicyFactory {

        private final T callback;
        private final long delay; //ms
        private final long requestTimeout; //ms
        private final long operationTimeout; //ms

        /**
         * Basic constructor with timeout and delay.
         *
         * @param requestTimeout   timeout for one retry attempt, in milliseconds
         * @param operationTimeout timeout for the whole operation, in milliseconds
         * @param delay            delay between retry attempts, in milliseconds
         * @param callback         predicate checking whether the given exception may be retried
         */
        public InfiniteRetryPolicyFactory(long requestTimeout, long operationTimeout, long delay, T callback) {
            this.callback = callback;
            this.delay = delay;
            this.requestTimeout = requestTimeout;
            this.operationTimeout = operationTimeout;
        }

        /**
         * Create a builder for this factory
         *
         * @param callback predicate checking whether the given exception may be retried
         * @param       exception checking callback function type
         * @return new builder instance
         */
        public static > Builder builder(T callback) {
            return new Builder<>(callback);
        }

        @Override
        public RequestRetryPolicy create() {
            return new InfiniteRetryPolicy<>(requestTimeout, operationTimeout, delay, callback);
        }

        /**
         * Builder for {@link InfiniteRetryPolicyFactory}
         *
         * @param  exception checking callback function type
         */
        public static class Builder> {

            private long requestTimeout = DEFAULT_ONE_HOUR_TIMEOUT; //ms
            private long delay; //ms
            private final T callback;
            private long operationTimeout = DEFAULT_ONE_HOUR_TIMEOUT; //ms

            /**
             * Basic constructor
             *
             * @param callback predicate checking whether the given exception may be retried
             */
            public Builder(T callback) {
                this.callback = callback;
            }

            /**
             * Set timeout for each attempt
             *
             * @param timeout task timeout, in milliseconds
             * @return this builder instance
             */
            public Builder withRequestTimeout(long timeout) {
                this.requestTimeout = timeout;
                return this;
            }

            public Builder withOperationTimeout(long operationTimeout) {
                this.operationTimeout = operationTimeout;
                return this;
            }

            /**
             * Set delay between attempts
             *
             * @param delay task delay, in milliseconds
             * @return this builder instance
             */
            public Builder withDelay(long delay) {
                this.delay = delay;
                return this;
            }

            /**
             * Create new factory instance
             *
             * @return new factory instance
             */
            public InfiniteRetryPolicyFactory build() {
                return new InfiniteRetryPolicyFactory<>(requestTimeout, operationTimeout, delay, callback);
            }
        }

        /**
         * Getter for exception handler
         *
         * @return exception handler
         */
        public T getCallback() {
            return this.callback;
        }

        /**
         * Getter for delay
         *
         * @return delay in milliseconds
         */
        public long getDelay() {
            return this.delay;
        }

        /**
         * Getter for request timout
         *
         * @return request timeout in milliseconds
         */
        public long getRequestTimeout() {
            return this.requestTimeout;
        }

        /**
         * Getter for operation timeout
         *
         * @return operation timeout in milliseconds
         */
        public long getOperationTimeout() {
            return operationTimeout;
        }
    }

    /**
     * Retry policy that accepts a maximum number of attempts and an exception checking predicate.
     * If the exception check passes and there are any attempts left, the policy returns {@code true}.
     *
     * @param  exception checking predicate type
     */
    public static final class AttemptsBoundRetryPolicy> implements RequestRetryPolicy {

        private int attempts;
        private final int limit;
        private final long requestTimeout; //ms
        private final long delay; //ms
        private final T exceptionCheck;

        @Override
        public long getRequestTimeout() {
            return requestTimeout;
        }

        /**
         * Basic constructor with timeout
         *
         * @param attempts       maximum number of retry attempts
         * @param requestTimeout timeout for one retry attempt, in milliseconds
         * @param delay          delay between attempts, in milliseconds
         * @param exceptionCheck predicate checking whether the given exception may be retried
         */
        public AttemptsBoundRetryPolicy(int attempts, long requestTimeout, long delay, T exceptionCheck) {
            Assert.state(attempts >= 0, "Attempts must be greater or equal than 0!");
            Assert.state(requestTimeout >= 0, "Timeout must be greater or equal than 0!");
            Assert.state(delay >= 0, "Timeout must be greater or equal than 0!");
            Assert.notNull(exceptionCheck, "Exception checking callback must not be null!");

            this.attempts = attempts;
            this.limit = attempts;
            this.requestTimeout = requestTimeout;
            this.delay = delay;
            this.exceptionCheck = exceptionCheck;
        }

        @Override
        public boolean canRetryRequest(Throwable throwable) {
            if (testException(exceptionCheck, throwable) && attempts > 0) {
                attempts--;
                if (delay > 0) {
                    trySleep(delay);
                }
                return true;
            }
            return false;
        }

        @Override
        public  CompletableFuture wrapOperation(Supplier> operation, Executor executor) {

            Assert.notNull(operation, "Operation must not be null");
            Assert.notNull(executor, "Executor must not be null");

            return CompletableFuture.supplyAsync(() -> {
                Throwable ex;
                do {
                    try {
                        return operation.get().get(getRequestTimeout(), TimeUnit.MILLISECONDS);
                    } catch (TimeoutException | InterruptedException e) {
                        ex = e;
                    } catch (ExecutionException e) {
                        ex = e.getCause();
                    }
                    if (attempts == 0) {
                        ex = new TarantoolAttemptsLimitException(
                            limit,
                            ex);
                        break;
                    }
                } while (this.canRetryRequest(ex));
                throw new CompletionException(ex);
            }, executor);
        }
    }

    /**
     * Factory for {@link AttemptsBoundRetryPolicy}
     *
     * @param  exception checking predicate type
     */
    public static final class AttemptsBoundRetryPolicyFactory>
        implements RequestRetryPolicyFactory {

        private final int numberOfAttempts;
        private final T exceptionCheck;
        private final long delay; //ms
        private final long requestTimeout; //ms

        /**
         * Basic constructor with timeout and delay
         *
         * @param numberOfAttempts maximum number of retry attempts
         * @param requestTimeout   timeout for one retry attempt, in milliseconds
         * @param delay            delay between retry attempts, in milliseconds
         * @param exceptionCheck   predicate checking whether the given exception may be retried
         */
        public AttemptsBoundRetryPolicyFactory(
            int numberOfAttempts,
            long requestTimeout,
            long delay,
            T exceptionCheck) {
            this.numberOfAttempts = numberOfAttempts;
            this.requestTimeout = requestTimeout;
            this.delay = delay;
            this.exceptionCheck = exceptionCheck;
        }

        /**
         * Create a builder for this factory
         *
         * @param attempts       maximum number of attempts
         * @param exceptionCheck function checking whether the given exception may be retried
         * @param             exception checking predicate type
         * @return new builder instance
         */
        public static > Builder builder(int attempts, T exceptionCheck) {
            return new Builder<>(attempts, exceptionCheck);
        }

        @Override
        public RequestRetryPolicy create() {
            return new AttemptsBoundRetryPolicy<>(numberOfAttempts, requestTimeout, delay, exceptionCheck);
        }

        /**
         * Builder for {@link AttemptsBoundRetryPolicyFactory}
         *
         * @param  exception checking predicate type
         */
        public static class Builder> {
            private final int numberOfAttempts;
            private long requestTimeout = DEFAULT_ONE_HOUR_TIMEOUT; //ms
            private long delay; //ms
            private final T exceptionCheck;

            /**
             * Basic constructor
             *
             * @param numberOfAttempts maximum number of retry attempts
             * @param exceptionCheck   predicate checking whether the given exception may be retried
             */
            public Builder(int numberOfAttempts, T exceptionCheck) {
                this.numberOfAttempts = numberOfAttempts;
                this.exceptionCheck = exceptionCheck;
            }

            /**
             * Set timeout for each attempt
             *
             * @param requestTimeout task timeout, in milliseconds
             * @return this builder instance
             */
            public Builder withRequestTimeout(long requestTimeout) {
                this.requestTimeout = requestTimeout;
                return this;
            }

            /**
             * Set delay between attempts
             *
             * @param delay task delay, in milliseconds
             * @return this builder instance
             */
            public Builder withDelay(long delay) {
                this.delay = delay;
                return this;
            }

            /**
             * Create new factory instance
             *
             * @return new factory instance
             */
            public AttemptsBoundRetryPolicyFactory build() {
                return new AttemptsBoundRetryPolicyFactory<>(numberOfAttempts, requestTimeout, delay, exceptionCheck);
            }
        }

        /**
         * Getter for number of attempts
         *
         * @return number of attempts
         */
        public int getNumberOfAttempts() {
            return numberOfAttempts;
        }

        /**
         * Getter for exception handler
         *
         * @return exception handler
         */
        public T getExceptionCheck() {
            return exceptionCheck;
        }

        /**
         * Getter for delay
         *
         * @return delay in milliseconds
         */
        public long getDelay() {
            return delay;
        }

        /**
         * Getter for request timeout
         *
         * @return request timeout in milliseconds
         */
        public long getRequestTimeout() {
            return requestTimeout;
        }
    }

    /**
     * Create a factory for retry policy bound by retry attempts.
     * The retry will be performed on any known network exceptions.
     *
     * @param numberOfAttempts maximum number of retries, zero value means no retries
     * @return new factory instance
     */
    public static AttemptsBoundRetryPolicyFactory.Builder>
    byNumberOfAttempts(int numberOfAttempts) {
        return byNumberOfAttempts(numberOfAttempts, retryNetworkErrors());
    }

    /**
     * Create a factory for retry policy bound by retry attempts
     *
     * @param numberOfAttempts maximum number of retries, zero value means no retries
     * @param exceptionCheck   predicate, checking the given exception whether the request may be retried
     * @param               exception checking predicate type
     * @return new factory instance
     */
    public static > AttemptsBoundRetryPolicyFactory.Builder
    byNumberOfAttempts(int numberOfAttempts, T exceptionCheck) {
        return AttemptsBoundRetryPolicyFactory.builder(numberOfAttempts, exceptionCheck);
    }

    /**
     * Create a factory for retry policy with unbounded number of attempts.
     * {@link #retryNetworkErrors} is used for checking exceptions by default.
     *
     * @param  exception checking predicate type
     * @return new factory instance
     */
    @SuppressWarnings("unchecked")
    public static > InfiniteRetryPolicyFactory.Builder unbound() {
        return unbound((T) retryNetworkErrors());
    }

    /**
     * Create a factory for retry policy with unbounded number of attempts
     *
     * @param exceptionCheck predicate, checking the given exception whether the request may be retried
     * @param             exception checking predicate type
     * @return new factory instance
     */
    public static > InfiniteRetryPolicyFactory.Builder
    unbound(T exceptionCheck) {
        return InfiniteRetryPolicyFactory.builder(exceptionCheck);
    }

    private static boolean testException(Predicate exceptionCheck, Throwable throwable) {
        try {
            return exceptionCheck.test(throwable);
        } catch (Exception e) {
            throw new TarantoolClientException(
                "Specified in TarantoolClient predicate for exception check threw exception: ", e);
        }
    }

    private static void trySleep(long delay) {
        try {
            TimeUnit.MILLISECONDS.sleep(delay);
        } catch (InterruptedException e) {
            throw new TarantoolClientException("Request retry delay has been interrupted");
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy