com.spotify.futures.AsyncRetrier Maven / Gradle / Ivy
/*
* Copyright (c) 2013-2018 Spotify AB
*
* Licensed 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
*
* http://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.spotify.futures;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* A helper for retrying asynchronous calls.
*
* It implements retries up to a specified limit with a constant delay between each retry.
*/
// TODO: Add support for specifying backoff behavior
public final class AsyncRetrier {
public static final long DEFAULT_DELAY_MILLIS = 0;
private final ScheduledExecutorService executorService;
private AsyncRetrier(ScheduledExecutorService executorService) {
this.executorService = executorService;
}
public static AsyncRetrier create(ScheduledExecutorService sleeper) {
return new AsyncRetrier(sleeper);
}
public ListenableFuture retry(final Supplier> code,
final int retries) {
return retry(code, retries, DEFAULT_DELAY_MILLIS, MILLISECONDS, Predicates.alwaysTrue());
}
public ListenableFuture retry(final Supplier> code,
final int retries,
final long delayMillis) {
return retry(code, retries, delayMillis, MILLISECONDS, Predicates.alwaysTrue());
}
public ListenableFuture retry(final Supplier> code,
final int retries,
final long delay,
final TimeUnit timeUnit) {
return retry(code, retries, delay, timeUnit, Predicates.alwaysTrue());
}
/**
* Retry {@code code} up to {@code retries} times.
*/
public ListenableFuture retry(final Supplier> code,
final int retries,
final long delay,
final TimeUnit timeUnit,
final Predicate retryCondition) {
SettableFuture future = SettableFuture.create();
startRetry(future, code, retries, delay, timeUnit, retryCondition);
return future;
}
private void startRetry(final SettableFuture future,
final Supplier> code,
final int retries,
final long delay,
final TimeUnit timeUnit,
final Predicate retryCondition) {
ListenableFuture codeFuture;
try {
codeFuture = code.get();
} catch (Exception e) {
handleFailure(future, code, retries, delay, timeUnit, retryCondition, e);
return;
}
Futures.addCallback(codeFuture, new FutureCallback() {
@Override
public void onSuccess(T result) {
if (retryCondition.apply(result)) {
future.set(result);
} else {
RuntimeException exception = new RuntimeException("Failed retry condition");
handleFailure(future, code, retries, delay, timeUnit, retryCondition, exception);
}
}
@Override
public void onFailure(Throwable t) {
handleFailure(future, code, retries, delay, timeUnit, retryCondition, t);
}
}, MoreExecutors.directExecutor());
}
private void handleFailure(final SettableFuture future,
final Supplier> code,
final int retries,
final long delay, final TimeUnit timeUnit,
final Predicate retryCondition,
Throwable t) {
if (retries > 0) {
executorService.schedule(
() -> startRetry(future, code, retries - 1, delay, timeUnit, retryCondition),
delay,
timeUnit);
} else {
future.setException(t);
}
}
}