Exponential Backoff with Java 8


Exponential backoff is an algorithm that uses feedback to multiplicatively decrease the rate of some process, in order to gradually find an acceptable rate. - Wikipedia

I recently used this strategy in work to deal with another service that we need to integrate. Sometimes, the service will just refuse the connection, without any reason. If I keep pushing, it will, someday, accept it.

So, I used Java 8 Functional Interfaces to implement this in a not-so-ugly way, also using a Fibonacci’s Sequence to increment the wait time:

The ExponentialBackOffFunction Functional Interface:

import java.rmi.RemoteException;

@FunctionalInterface
public interface ExponentialBackOffFunction<T> {
	T execute();
}

The ExponentialBackOff main class:

import static java.util.Arrays.asList;

import java.net.SocketTimeoutException;
import java.util.List;

import javax.net.ssl.SSLHandshakeException;

import lombok.extern.log4j.Log4j;

@Log4j
public final class ExponentialBackOff {
	private static final int[] FIBONACCI = new int[] { 1, 1, 2, 3, 5, 8, 13 };
	private static final List<Class<? extends Exception>> EXPECTED_COMMUNICATION_ERRORS = asList(
			SSLHandshakeException.class, SocketTimeoutException.class );

	private ExponentialBackOff() {

	}

	public static <T> T execute(ExponentialBackOffFunction<T> fn) {
    for (int attempt = 0; attempt < FIBONACCI.length; attempt++) {
			try {
				return fn.execute();
			} catch (Exception e) {
				handleFailure( attempt, e );
			}
		}
		throw new RuntimeException( "Failed to communicate." );
	}

	private static void handleFailure(int attempt, RemoteException e) {
		if (e.getCause() != null && !EXPECTED_COMMUNICATION_ERRORS.contains( e.getCause().getClass() ))
			throw new RuntimeException( e );
		doWait( attempt );
	}

	private static void doWait(int attempt) {
		try {
			Thread.sleep( FIBONACCI[attempt] * 1000 );
		} catch (InterruptedException e) {
			throw new RuntimeException( e );
		}
	}
}

Usage:

ExponentialBackOff.execute( () -> work() );

This will try to execute the work method incrementing the time between each call that fail with an expected error.

Related Posts

Fast and easy Go binaries delivery

Watchub

Fixing alerts