Design pattern for "retrying" logic that failed?

JavaDesign PatternsIo

Java Problem Overview


I'm writing some reconnect logic to periodically attempt to establish a connection to a remote endpoint which went down. Essentially, the code looks like this:

public void establishConnection() {
    try {
        this.connection = newConnection();
    } catch (IOException e) {
        // connection failed, try again.
        try { Thread.sleep(1000); } catch (InterruptedException e) {};
        
        establishConnection();
    }
}

I've solved this general problem with code similar to the above on many occasions, but I feel largely unsatisfied with the result. Is there a design pattern designed for dealing with this issue?

Java Solutions


Solution 1 - Java

Shameless plug: I have implemented some classes to allow retrying operations. The library is not made available yet, but you may fork it on github. And a fork exists.

It allows building a Retryer with various flexible strategies. For example:

Retryer retryer = 
    RetryerBuilder.newBuilder()
                  .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECOND))
                  .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                  .retryIfExceptionOfType(IOException.class)
                  .build();

And you can then execute a callable (or several ones) with the Retryer:

retryer.call(new Callable<Void>() {
    public Void call() throws IOException {
        connection = newConnection();
        return null;
    }
}

Solution 2 - Java

You could try the Idempotent Retry Pattern.

enter image description here

Solution 3 - Java

I really like this Java 8 code from this blog and you don't need any extra library on your classpath.

You only need to pass a function to the retry class.

@Slf4j
public class RetryCommand<T> {

	private int maxRetries;

	RetryCommand(int maxRetries)
	{
		this.maxRetries = maxRetries;
	}

	// Takes a function and executes it, if fails, passes the function to the retry command
	public T run(Supplier<T> function) {
		try {
			return function.get();
		} catch (Exception e) {
			log.error("FAILED - Command failed, will be retried " + maxRetries + " times.");
			return retry(function);
		}
	}

	private T retry(Supplier<T> function) throws RuntimeException {

		int retryCounter = 0;
		while (retryCounter < maxRetries) {
			try {
				return function.get();
			} catch (Exception ex) {
				retryCounter++;
				log.error("FAILED - Command failed on retry " + retryCounter + " of " + maxRetries, ex);
				if (retryCounter >= maxRetries) {
					log.error("Max retries exceeded.");
					break;
				}
			}
		}
		throw new RuntimeException("Command failed on all of " + maxRetries + " retries");
	}
}

And to use it:

new RetryCommand<>(5).run(() -> client.getThatThing(id));

Solution 4 - Java

Using Failsafe (author here):

RetryPolicy retryPolicy = new RetryPolicy()
  .retryOn(IOException.class)
  .withMaxRetries(5)
  .withDelay(1, TimeUnit.SECONDS);

Failsafe.with(retryPolicy).run(() -> newConnection());

No annotations, no magic, doesn't need to be a Spring app, etc. Just straightforward and simple.

Solution 5 - Java

I'm using AOP and Java annotations. There is a ready-made mechanism in jcabi-aspects (I'm a developer):

@RetryOnFailure(attempts = 3, delay = 1, unit = TimeUnit.SECONDS)
public void establishConnection() {
  this.connection = newConnection();
}

ps. You can also try RetryScalar from Cactoos.

Solution 6 - Java

You can try spring-retry, it has a clean interface and it's easy to use.

Example:

 @Retryable(maxAttempts = 4, backoff = @Backoff(delay = 500))
 public void establishConnection() {
    this.connection = newConnection();
 } 

In case of exception, it will retry (call) up to 4 times the method establishConnection() with a backoff policy of 500ms

Solution 7 - Java

One library worth checkout out is Sarge, which automatically performs retries according to a defined plan.

Solution 8 - Java

You can also create a wrapper function that just does a loop over the intended operation and when is done just break out of the loop.

public static void main(String[] args) {
    retryMySpecialOperation(7);
}

private static void retryMySpecialOperation(int retries) {
    for (int i = 1; i <= retries; i++) {
        try {
            specialOperation();
            break;
        }
        catch (Exception e) {
            System.out.println(String.format("Failed operation. Retry %d", i));
        }
    }
}

private static void specialOperation() throws Exception {
    if ((int) (Math.random()*100) % 2 == 0) {
        throw new Exception("Operation failed");
    }
    System.out.println("Operation successful");
}

Solution 9 - Java

If you are using java 8, this may helps.

import java.util.function.Supplier;

public class Retrier {
public static <T> Object retry(Supplier<T> function, int retryCount) throws Exception {
	 while (0<retryCount) {
		try {
			return function.get();
		} catch (Exception e) {
			retryCount--;
			if(retryCount == 0) {
				throw e;
			}
		}
	}
	return null;
}

public static void main(String[] args) {
	try {
		retry(()-> {
			System.out.println(5/0);
			return null;
		}, 5);
	} catch (Exception e) {
		System.out.println("Exception : " + e.getMessage());
	}
}
}

Thanks,

Praveen R.

Solution 10 - Java

there is nothing special in retrying at all - take this class as example http://www.docjar.com/html/api/org/springframework/jms/listener/DefaultMessageListenerContainer.java.html As you can see even spring developers still writing code for retry-ing - line 791... there is no such special pattern AFAIK..

What i can advice to deal with resources is to take apache commons pool library - check this http://commons.apache.org/pool/apidocs/org/apache/commons/pool/impl/GenericObjectPool.html and visit http://commons.apache.org/pool

Solution 11 - Java

I'm using retry4j library. Test code example:

public static void main(String[] args) {
	Callable<Object> callable = () -> {
		doSomething();
		return null;
	};

	RetryConfig config = new RetryConfigBuilder()
			.retryOnAnyException()
			.withMaxNumberOfTries(3)
			.withDelayBetweenTries(5, ChronoUnit.SECONDS)
			.withExponentialBackoff()
			.build();

	new CallExecutorBuilder<>().config(config).build().execute(callable);
}

public static void doSomething() {
	System.out.println("Trying to connect");
	// some logic
	throw new RuntimeException("Disconnected"); // init error
	// some logic
}

Solution 12 - Java

Here's a another approach to perform the retry. No libraries, no annotations, no extra implementations. Import java.util.concurrent.TimeUnit;

public static void myTestFunc() {
		boolean retry = true;
		int maxRetries = 5;   //max no. of retries to be made
		int retries = 1;
		int delayBetweenRetries = 5;  // duration  between each retry (in seconds)
	    int wait = 1;
	do {
		try {
			this.connection = newConnection();
			break;
		}
		catch (Exception e) {
			wait = retries * delayBetweenRetries;
			pause(wait);
			retries += 1;
			if (retries > maxRetries) {
				retry = false;
				log.error("Task failed on all of " + maxRetries + " retries");
			}
		}
	} while (retry);

}

public static void pause(int seconds) {
	long secondsVal = TimeUnit.MILLISECONDS.toMillis(seconds);

	try {
		Thread.sleep(secondsVal);
	}
	catch (InterruptedException ex) {
		Thread.currentThread().interrupt();
	}
}

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNaftuli KayView Question on Stackoverflow
Solution 1 - JavaJB NizetView Answer on Stackoverflow
Solution 2 - JavaGalacticJelloView Answer on Stackoverflow
Solution 3 - JavaDherikView Answer on Stackoverflow
Solution 4 - JavaJonathanView Answer on Stackoverflow
Solution 5 - Javayegor256View Answer on Stackoverflow
Solution 6 - Javadb80View Answer on Stackoverflow
Solution 7 - JavaJonathanView Answer on Stackoverflow
Solution 8 - JavaklushtView Answer on Stackoverflow
Solution 9 - JavaRavipati PraveenView Answer on Stackoverflow
Solution 10 - JavaAndrey BorisovView Answer on Stackoverflow
Solution 11 - JavaValeriy K.View Answer on Stackoverflow
Solution 12 - JavaVishal KhardeView Answer on Stackoverflow