Quantcast
Channel: biotext.org.uk » Research
Viewing all articles
Browse latest Browse all 4

RetriableTask — a generic wrapper for retrying operations in Java

$
0
0

A couple of times recently I’ve needed to write methods that retry up to n times if an error occurs, and surprisingly, couldn’t find any standard patterns to accomplish this on the web. So I wrote my own. All comments appreciated (there may well be massive holes in my logic but it works so far).

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.logging.Logger;

/**
 * This class is a wrapper for a Callable that adds retry functionality.
 * The user supplies an existing Callable, a maximum number of tries,
 * and optionally a Logger to which exceptions will be logged. Calling
 * the call() method of RetriableTask causes the wrapped object's call()
 * method to be called, and any exceptions thrown from the inner call()
 * will cause the entire inner call() to be repeated from scratch, as
 * long as the maximum number of tries hasn't been exceeded.
 * InterruptedException and CancellationException are allowed to
 * propogate instead of causing retries, in order to allow cancellation
 * by an executor service etc.
 *
 * @param <T> the return type of the call() method
 */
public class RetriableTask<T> implements Callable<T>
{

    private final Callable<T> _wrappedTask;

    private final int _tries;

    private final Logger _log;

    /**
     * Creates a new RetriableTask around an existing Callable. Supplying
     * zero or a negative number for the tries parameter will allow the
     * task to retry an infinite number of times -- use with caution!
     *
     * @param taskToWrap the Callable to wrap
     * @param tries the max number of tries
     * @param log a Logger to log exceptions to (null == no logging)
     */
    public RetriableTask ( final Callable<T> taskToWrap, final int tries, final Logger log )
    {
        _wrappedTask = taskToWrap;
        _tries = tries;
        _log = log;
    }

    /**
     * Invokes the wrapped Callable's call method, optionally retrying
     * if an exception occurs. See class documentation for more detail.
     *
     * @return the return value of the wrapped call() method
     */
    public T call() throws Exception
    {
        int triesLeft = _tries;
        while( true )
        {
            try
            {
                return _wrappedTask.call();
            }
            catch( final InterruptedException e )
            {
                // We don't attempt to retry these
                throw e;
            }
            catch( final CancellationException e )
            {
                // We don't attempt to retry these either
                throw e;
            }
            catch( final Exception e )
            {
                triesLeft--;

                // Are we allowed to try again?
                if( triesLeft == 0 ) // No -- rethrow
                    throw e;

                // Yes -- log and allow to loop
                if( _log != null )
                    _log.warning( "Caught exception, retrying... Error was: " + e.getMessage() );
            }
        }

    }

}

EDIT: Just to make it clear how I would use this, it works best in the context of an ExecutorService, which manages multi-threading and timeouts for you. So for example, I could create an executor service like so:

ExecutorService pool = Executors.newCachedThreadPool();

Then create all the tasks I need, and wrap them in RetriableTasks:

// Create a task that returns a String
Callable<String> task1 = new Callable<String>()
{
    public String call()
    {
        // Do stuff here
    }
};
// Make it try up to three times
RetriableTask<String> retriable1 =
    new RetriableTask<String>( task1, 3, null );

Then add all the RetriableTasks to a Collection, and execute them all on the thread pool:

Collection<Callable<String>> tasks =
    new ArrayList<Callable<String>>();
tasks.add( retriable1 );
// TIMEOUT == timeout in seconds
List<Future<String>> results =
    pool.invokeAll( tasks, TIMEOUT, TimeUnit.SECONDS );

Finally, iterate through the results list to get all the return values:

for( Future<String> result : results )
{
    // Un-retried exceptions will pop out here
    String returnValue = result.get();
}

Or use a CompletionService.

Obviously there’s lots of other things you could do within the same framework — introducing a delay before retrying would be useful in a lot of circumstances, and also you could supply a list of exceptions that would be allowed to propagate. Or only retry on checked exceptions, and automatically bubble unchecked ones. Really it depends on the use case.

Feel free to write an extended version and post it here or link to it :-)

Andrew.


Viewing all articles
Browse latest Browse all 4

Trending Articles