io.tarantool.driver.api.retry.TarantoolRequestRetryPolicies Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cartridge-driver Show documentation
Show all versions of cartridge-driver Show documentation
Tarantool Cartridge driver for Tarantool versions 1.10+ based on Netty framework
package io.tarantool.driver.api.retry;
import io.tarantool.driver.core.TarantoolDaemonThreadFactory;
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.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
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 {
private static final ScheduledExecutorService timeoutScheduler =
Executors.newSingleThreadScheduledExecutor(new TarantoolDaemonThreadFactory("tarantool-retry-timeout"));
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)) {
return true;
}
return false;
}
@Override
public long getRequestTimeout() {
return requestTimeout;
}
@Override
public long getDelay() {
return delay;
}
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");
// because we have asynchronous logic in completion stage chain
// we should have sharing answer state for final result
CompletableFuture resultFuture = new CompletableFuture<>();
// to provide it if retrying has been stopped without correct result
AtomicReference lastExceptionWrapper = new AtomicReference<>();
CompletableFuture.runAsync(() -> {
runAsyncOperation(operation, resultFuture, lastExceptionWrapper);
// set global timeout
ScheduledFuture> operationTimeoutScheduledFuture =
TarantoolRequestRetryPolicies.getTimeoutScheduler().schedule(() -> {
if (!resultFuture.isDone()) {
Throwable lastException = lastExceptionWrapper.get();
if (lastException != null) {
resultFuture
.completeExceptionally(
new TarantoolTimeoutException(operationTimeout, lastException));
} else {
resultFuture
.completeExceptionally(new TarantoolTimeoutException(operationTimeout));
}
}
}, operationTimeout, TimeUnit.MILLISECONDS);
// optimization: stop scheduled future if resultFuture has already done
resultFuture.whenComplete((res, ex) -> operationTimeoutScheduledFuture.cancel(false));
}, executor)
.exceptionally(ex -> { // we should complete final exception if something went wrong
resultFuture.completeExceptionally(ex);
return null;
});
return resultFuture;
}
}
/**
* 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;
}
@Override
public long getDelay() {
return delay;
}
/**
* 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--;
return true;
}
return false;
}
@Override
public void runAsyncOperation(
Supplier> operation, CompletableFuture resultFuture,
AtomicReference lastExceptionWrapper) {
// start async operation running
CompletableFuture operationFuture = operation.get();
// start scheduled request timeout task
// it never completes correctly only exceptionally
CompletableFuture requestTimeoutFuture = failAfterRequestTimeout(resultFuture);
operationFuture.acceptEither(requestTimeoutFuture, resultFuture::complete)
.exceptionally(ex -> { // if requestTimeout has been raised or operation return exception
while (ex instanceof ExecutionException || ex instanceof CompletionException) {
ex = ex.getCause();
}
// to provide it if retrying has been stopped without correct result
lastExceptionWrapper.set(ex);
if (attempts == 0) {
ex = new TarantoolAttemptsLimitException(
limit,
ex);
resultFuture.completeExceptionally(ex);
return null;
}
if (this.canRetryRequest(ex)) {
// retry it after delay
ScheduledFuture> delayFuture =
TarantoolRequestRetryPolicies.getTimeoutScheduler().schedule(() -> {
runAsyncOperation(operation, resultFuture, lastExceptionWrapper);
}, getDelay(), TimeUnit.MILLISECONDS);
// optimization: stop delayed future if resultFuture has already done from outside
resultFuture.whenComplete((r, e) -> delayFuture.cancel(false));
} else {
resultFuture.completeExceptionally(ex);
}
return null;
}).exceptionally(ex -> { // if error has been happened in previous exceptionally section
resultFuture.completeExceptionally(ex);
return null;
});
}
}
/**
* 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);
}
}
/**
* Get timeout scheduler instance.
*
* @return scheduler instance
*/
public static ScheduledExecutorService getTimeoutScheduler() {
return timeoutScheduler;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy