biotext.org.uk

RetriableTask — a generic wrapper for retrying operations in Java

by Andrew on May.20, 2009, under Research, Tips

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.

Share/save this page:
  • email
  • Print
  • Google Bookmarks
  • del.icio.us
  • Digg
  • Reddit
  • StumbleUpon
  • Technorati
  • DZone
  • Slashdot
  • Fark
  • Facebook
  • MySpace
  • LinkedIn
  • Live
  • connotea
:,
11 comments for this entry:
  1. rzei

    Having implemented a same kind of retry functionality around Callable I’d like to note that specifying a “RetryingStrategy” interface that has only one method like “isRetryable(RetryingTask task, Exception e)” would make your impl a bit more reusable.

    For example with Spring’s DataAccessException hierarchy it’s trivial to implement a DataAccessRetryingStrategy that will retry on TransientDataAccessExceptions (for example database deadlock).

    One more step might be the ability of RetryingStrategy implementors to control the retrying; ie. “makeNextAttemptLast()” or “allowOneMoreAttempt()”.

  2. florin

    Wouldn’t you want this in its own thread, asynchronously especially if you put a timer on it? Also, a callback might be needed rather than a return on the call() method.

  3. Marcio

    @florin

    Because it’s a Callable nothing stops you from executing it in an ExecutorService.

  4. Andrew

    @Marcio — exactly, that’s what I intended. You get parallelism and timeouts for free that way. I should put mention that in the post really.

    @rzei — sounds like that’s starting to grow into a proper framework rather than a one class solution :-) I’m not sure why you think this method lacks reusability though.

  5. Jkilgrow

    I wrote something very similar for calling webservices that were particularly unreliable.

  6. Andrew

    @Jkilgrow — heh, web services are exactly why I wrote this :-)

  7. mccv

    Funny, I had a similar requirement and came up with a similar approach. My lack of familiarity with Callable shows, as I basically created the same interface. I put up a post comparing the implementation in Java vs. an implementation in Scala here http://www.themcwongs.com/?p=50

  8. Andrew

    @mccv — It did occur to me as I was writing it that it would be much easier in a language where you could pass function calls around…

    Callable is well worth getting to know though, as are ExecutorService, Future and FutureTask. Even if you don’t do industrial-grade concurrency, they give you some handy options for breaking your code into units of work.

  9. A slightly cleaner Java Retryable - mccv

    [...] implementation of a class that retries an operation N times before failing. However after reading this post I realized that using Java’s Callable class cleans things up quite a bit. This completely [...]

  10. Bob Carpenter

    For web downloads, you often have a whole set of URLs to download. It’s bad form to run multiple concurrent downloads against the same site/server.

    So the usual approach to doing this runs a multi-threaded scheduling priority queue that orders things to download by time-to-next-try and doesn’t allow concurrent downloads from the same site (basically a set-based semaphore setup). Typically, under errors, there will be some scheme to wait longer before the next try. And often scheduling that depends on the last time you found fresh content.

    I think rzei’s right, that at least a more generic abstract base class (not a whole framework!) might be useful here. A one-class alternative would be to take a set of exception Class objects, with exceptions in the set not being retried.

  11. Andrew

    Yep, I can see this might require a slightly different approach if you’re doing webcrawling. In our case, we’re using this to dispatch parallel SOAP messages to various different web services living on different servers, so even before I brought this class in, we were still wrapping all the SOAP client operations in Callables and running them on a thread pool.

    As Bob says, it would get more involved if you don’t want multiple threads contacting the same server at once. Although a quick way to adapt this class, for simple cases, would be to use a separate single-threaded executor for each server.

    I love the concurrency framework.

Leave a Reply

Search

Use the form below to search the site:

Leave a comment if you can't find what you need.