Functionality for automatic retry after exception

Posted on

Problem

I have made the following system to automatically retry network calls if some exception is thrown. (Earlier posted on stackOverFlow, solution inspired by what @Boris answered)

public static <T> Optional<T> autoRetry(@NonNull final DoWork<T> task, @NonNull final Optional<Predicate<T>> resultRejector) {

        for (int retry = 0; retry <= StaticData.NETWORK_RETRY; ++retry) {

            try {

                Thread.sleep(retry * StaticData.NETWORK_CALL_WAIT);
                final T resultAfterWork = task.doWork();
                /**
                 * If the result was not
                 * desirable we RETRY.
                  */
                if(resultRejector.isPresent() && resultRejector.get().apply(resultAfterWork))
                    continue;
                /**
                 * Else we return
                 */
                return Optional.fromNullable(resultAfterWork);
            } catch (InterruptedException | UnknownHostException e) {
                e.printStackTrace(); //To be replaced by proper logging
                return Optional.absent();
            } catch (IOException e) {
                e.printStackTrace(); //To be replaced by proper logging
            }
        }
        return Optional.absent();
    }
  • I take care to not retry after InterruptedException &
    UnknownHostException.
  • I retry 5 times. After each failure I perform an exponential back
    off, starting from 300ms going upto 1500ms.
  • The predicate is used to verify the validity of the expected result.

The DoWork class :

public abstract class DoWork<T> {
    protected abstract T doWork() throws IOException;
}

I did not use Supplier because I need to throw an IOException.

Here is an example of how I use it. (this example updates the GCM Id)

//Context is android specific stuff, basically if app dies, reference will return null.

public static boolean updateGCM(final long id, final WeakReference<Context> reference) {

final String regId = autoRetry(new DoWork<String>() {
    @Override
    protected String doWork() throws IOException {

        final Context context = reference.get();
        if(context == null)
            return "QUIT";
        return GoogleCloudMessaging.getInstance(context)
                .register("XXXXXXXXXX");
    }
}, Optional.<Predicate<String>>of(new Predicate<String>() {
    @Override
    public boolean apply(@Nullable String input) {
        return TextUtils.isEmpty(input); //I need to retry if the returned GCM ID was null/empty
    }
})).orNull();
//Context becomes null when the application is exited
//If retires failed or application exited, we return
if(TextUtils.isEmpty(regId) || regId.equals("QUIT"))
    return false;

Log.i("Ayush", "Uploading newGcmId to server");
final Boolean result = autoRetry(new DoWork<Boolean>() {
    @Override
    protected Boolean doWork() throws IOException {

        //if everything is fine, send id to server
        return true;
    }
}, Optional.<Predicate<Boolean>>absent()).orNull(); //No need of predicate hence send absent

return !(result == null || !result);
}
  • So on a scale of 1 to 10 how professional is this approach ? 🙂
  • Also I do not want to retry if the network is genuinely failing, like
    UnknownHostException, or something bad happens which wont get
    resolved with retrying. Any possibility of improving exception handling ?

Solution

public static <T> Optional<T> autoRetry(@NonNull final DoWork<T> task, @NonNull final Optional<Predicate<T>> predicate) {

No predicate, please. Call it something like acceptResult or resultRejector… you know what I mean.

Thread.sleep(retry * StaticData.NETWORK_CALL_WAIT);

This doesn’t do what you write, as the first time retry=0.

I take care to not retry after InterruptedException & UnknownHostException.

The latter may be retryable as it may be a temporary network or DNS problem. But it’s your choice.

final String regId = autoRetry(new DoWork<String>() {...

I’m too lazy to copy it in my Eclipse and without it, it’s unreadable as I have no idea where the expressions ends. What about a private final DoWork<String> regIdDoWork = ... or alike?

Optional.<Predicate<Boolean>>absent()).orNull(); //No need of predicate hence send absent

How nicer would the world be if there was no Optional? 😀 Just null.

Note that even if you like Optional, you should probably use it for as return types only. This is the place where it may prevent bugs and where it doesn’t add so much verbosity.

Leave a Reply

Your email address will not be published. Required fields are marked *