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

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