Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retry a method based on result (instead of exception)

I have a method with the following signature:

public Optional<String> doSomething() {
    ...
}

If I get an empty Optional I'd like to retry this method and only after 3 times return the empty Optional.

I've looked and found the Retryable spring annotation, but it seems to only work on Exceptions.

If possible I'd like to use a library for this, and avoid:

  • Creating and throwing an exception.
  • Writing the logic myself.
like image 841
orirab Avatar asked Feb 04 '19 17:02

orirab


People also ask

How do you retry a method in Java?

1. Simple for-loop with try-catch. A simple solution to implement retry logic in Java is to write your code inside a for loop that executes the specified number of times (the maximum retry value).

What is a Retryable exception?

A retryable exception is a transient exception that if retried may succeed.

How do I enable retry in spring boot?

First, you need to enable Spring Retry. You can achieve this by adding the @EnableRetry annotation to your @SpringBootApplication or @Configuration class. You can now use @Retryable to annotate any method to be a candidate or retry and @Recover to specify fallback methods.

What is @retryable in spring boot?

Spring Retry provides the ability to automatically re-invoke a failed operation. This is helpful when errors may be transient in nature. For example, a momentary network glitch, network outage, server down, or deadlock. You can configure the. spring-retry.


2 Answers

I have been using failsafe build in retry. You can retry based on predicates and exceptions.

Your code would look like this:

    private Optional<String> doSomethingWithRetry() {
        RetryPolicy<Optional> retryPolicy = new RetryPolicy<Optional>()
                .withMaxAttempts(3)
                .handleResultIf(result -> {
                    System.out.println("predicate");
                    return !result.isPresent();
                });

        return Failsafe
                .with(retryPolicy)
                .onSuccess(response -> System.out.println("ok"))
                .onFailure(response -> System.out.println("no ok"))
                .get(() -> doSomething());
    }

    private Optional<String> doSomething() {
         return Optional.of("result");
    }

If the optional is not empty the output is:

predicate
ok

Otherwise looks like:

predicate
predicate
predicate
no ok
like image 197
Cristian Rodriguez Avatar answered Sep 30 '22 05:09

Cristian Rodriguez


@Retryable (and the underlying RetryTemplate) are purely based on exceptions.

You could subclass RetryTemplate, overriding doExecute() to check the return value.

You would probably have to replicate much of the code in the method; it's not really designed for overriding just the retryCallback.doWithRetry() call.

You can use a custom RetryTemplate in a RetryOperationsInterceptor (specified in the @Retryable in the interceptor property).

EDIT

The current RetryTemplate code looks like this...

while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {

    try {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Retry: count=" + context.getRetryCount());
        }
        // Reset the last exception, so if we are successful
        // the close interceptors will not think we failed...
        lastException = null;
        return retryCallback.doWithRetry(context);
    }
    catch (Throwable e) {

        lastException = e;

        try {
            registerThrowable(retryPolicy, state, context, e);
        }
        catch (Exception ex) {
            throw new TerminatedRetryException("Could not register throwable",
                    ex);
        }
        finally {
            doOnErrorInterceptors(retryCallback, context, e);
        }

         ... 

    }

You would need to change it to something like...

while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {

    try {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Retry: count=" + context.getRetryCount());
        }
        // Reset the last exception, so if we are successful
        // the close interceptors will not think we failed...
        lastException = null;
        T result = retryCallback.doWithRetry(context);
        if (((Optional<String>) result).get() == null) {

            try {
                registerThrowable(retryPolicy, state, context, someDummyException);
            }
            catch (Exception ex) {
                throw new TerminatedRetryException("Could not register throwable",
                        ex);
            }
            finally {
                doOnErrorInterceptors(retryCallback, context, e);
            }

            ...
        }
        else {
            return result;
        }
    }
    catch (Throwable e) {

       ...

    }

Where someDummyException is to fool the context into incrementing the counter. It can be a static field, just created once.

like image 28
Gary Russell Avatar answered Sep 30 '22 03:09

Gary Russell