
net.leanix.dropkit.util.async.RetryExecutor Maven / Gradle / Ivy
package net.leanix.dropkit.util.async;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A helper executor class used to execute a block of code in another thread with a retry mechanism. In case the code block
* which is invoked in {@linkplain #run(RetryTask)} throws an exception, this block will be executed again after a defined
* delay until no exception arises.
*
* Here is an example, how to use this class for a reliable webhooks subscription when a microservice starts:
*
*
* {@literal @}Singleton
* public class WebhooksSubscriber implements Managed {
*
* private final RetryExecutor retryExecutor;
*
* {@literal @}Inject
* public WebhooksSubscriber(AppConfiguration appConfiguration) {
* this.retryExecutor = new RetryExecutor();
* }
*
* protected void upsertSubscription(Subscription subscription) {
* retryExecutor.run((attemptCount) -> {
* try {
* Subscription existingSubscription = getSubscriptions(null, subscription.getIdentifier());
* if (existingSubscription == null) {
* LOG.info("creating new webhooks subscription '{}'", subscription);
* subscriptionsApi.createSubscription(subscription);
* } else {
* LOG.debug("refreshing webhooks subscription '{}'", subscription);
* subscriptionsApi.updateSubscription(existingSubscription.getId(), subscription);
* }
* } catch (ApiException e) {
* LOG.info("Could not subscribe to webhooks with attempt {}", attemptCount, e);
* throw e;
* }
* });
* }
*
* {@literal @}Override
* public void stop() throws Exception {
* retryExecutor.stop();
* }
* ...
* }
*
*
* @author ralfwehner
*
*/
public class RetryExecutor {
private static final Logger LOG = LoggerFactory.getLogger(RetryExecutor.class);
private final ScheduledExecutorService scheduleExecutorService;
private final int delayAfterFail;
private final TimeUnit delayAfterFailUnit;
public RetryExecutor() {
this(1, 2, TimeUnit.SECONDS, "RetryExecutor-%d");
}
public RetryExecutor(int corePoolSize, int delayAfterFail, TimeUnit delayAfterFailUnit, String patternThreadFactory) {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat(patternThreadFactory)
.setDaemon(true)
.build();
this.scheduleExecutorService = new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
this.delayAfterFail = delayAfterFail;
this.delayAfterFailUnit = delayAfterFailUnit;
}
public void stop() throws InterruptedException {
LOG.info("stopping scheduleExecutorService");
scheduleExecutorService.shutdownNow();
scheduleExecutorService.awaitTermination(10, TimeUnit.SECONDS);
}
public void run(RetryTask task) {
submitInternal(new AtomicInteger(0), task, 0, TimeUnit.MILLISECONDS);
}
private void submitInternal(AtomicInteger callCount, RetryTask task, long delay, TimeUnit delayTimeUnit) {
// schedule a task that completes the future starting with given delay
scheduleExecutorService.schedule(() -> {
try {
task.call(callCount.get());
} catch (Throwable e) {
LOG.debug("runnable ({}) throws exception, restart runnable again", callCount.get(), e);
callCount.incrementAndGet();
submitInternal(callCount, task, delayAfterFail, delayAfterFailUnit);
}
}, delay, delayTimeUnit);
}
@FunctionalInterface
public interface RetryTask {
/**
* Contains the code block, which should be executed.
*
* @param attemptCount A counter starting with zero and is increased in the case that this method throws an exception and is therefore
* called again.
* @throws Exception An exception, which might occur during processing of the code block. When an exception is thrown, this method
* will be called again with {@code attemptCount + 1}.
* called
*/
void call(int attemptCount) throws Exception;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy