Code Monkey home page Code Monkey logo

spring-retry's Introduction

ci.spring.io Javadocs

This project provides declarative retry support for Spring applications. It is used in Spring Batch, Spring Integration, and others. Imperative retry is also supported for explicit usage.

Quick Start

This section provides a quick introduction to getting started with Spring Retry. It includes a declarative example and an imperative example.

Declarative Example

The following example shows how to use Spring Retry in its declarative style:

@Configuration
@EnableRetry
public class Application {

}

@Service
class Service {
    @Retryable(retryFor = RemoteAccessException.class)
    public void service() {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e) {
       // ... panic
    }
}

This example calls the service method and, if it fails with a RemoteAccessException, retries (by default, up to three times), and then tries the recover method if unsuccessful. There are various options in the @Retryable annotation attributes for including and excluding exception types, limiting the number of retries, and setting the policy for backoff.

The declarative approach to applying retry handling by using the @Retryable annotation shown earlier has an additional runtime dependency on AOP classes. For details on how to resolve this dependency in your project, see the 'Java Configuration for Retry Proxies' section.

Imperative Example

The following example shows how to use Spring Retry in its imperative style (available since version 1.3):

RetryTemplate template = RetryTemplate.builder()
				.maxAttempts(3)
				.fixedBackoff(1000)
				.retryOn(RemoteAccessException.class)
				.build();

template.execute(ctx -> {
    // ... do something
});

For versions prior to 1.3, see the examples in the RetryTemplate section.

Building

Spring Retry requires Java 1.7 and Maven 3.0.5 (or greater). To build, run the following Maven command:

$ mvn install

Features and API

This section discusses the features of Spring Retry and shows how to use its API.

Using RetryTemplate

To make processing more robust and less prone to failure, it sometimes helps to automatically retry a failed operation, in case it might succeed on a subsequent attempt. Errors that are susceptible to this kind of treatment are transient in nature. For example, a remote call to a web service or an RMI service that fails because of a network glitch or a DeadLockLoserException in a database update may resolve itself after a short wait. To automate the retry of such operations, Spring Retry has the RetryOperations strategy. The RetryOperations interface definition follows:

public interface RetryOperations {

	<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E;

	<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback) throws E;

	<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState) throws E, ExhaustedRetryException;

	<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState) throws E;

}

The basic callback is a simple interface that lets you insert some business logic to be retried:

public interface RetryCallback<T, E extends Throwable> {

    T doWithRetry(RetryContext context) throws E;

}

The callback is tried, and, if it fails (by throwing an Exception), it is retried until either it is successful or the implementation decides to abort. There are a number of overloaded execute methods in the RetryOperations interface, to deal with various use cases for recovery when all retry attempts are exhausted and to deal with retry state, which lets clients and implementations store information between calls (more on this later).

The simplest general purpose implementation of RetryOperations is RetryTemplate. The following example shows how to use it:

RetryTemplate template = new RetryTemplate();

TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);

template.setRetryPolicy(policy);

MyObject result = template.execute(new RetryCallback<MyObject, Exception>() {

    public MyObject doWithRetry(RetryContext context) {
        // Do stuff that might fail, e.g. webservice operation
        return result;
    }

});

In the preceding example, we execute a web service call and return the result to the user. If that call fails, it is retried until a timeout is reached.

Since version 1.3, fluent configuration of RetryTemplate is also available, as follows:

RetryTemplate.builder()
      .maxAttempts(10)
      .exponentialBackoff(100, 2, 10000)
      .retryOn(IOException.class)
      .traversingCauses()
      .build();

RetryTemplate.builder()
      .fixedBackoff(10)
      .withinMillis(3000)
      .build();

RetryTemplate.builder()
      .infiniteRetry()
      .retryOn(IOException.class)
      .uniformRandomBackoff(1000, 3000)
      .build();

Using RetryContext

The method parameter for the RetryCallback is a RetryContext. Many callbacks ignore the context. However, if necessary, you can use it as an attribute bag to store data for the duration of the iteration. It also has some useful properties, such as retryCount.

A RetryContext has a parent context if there is a nested retry in progress in the same thread. The parent context is occasionally useful for storing data that needs to be shared between calls to execute.

If you don't have access to the context directly, you can obtain the current context within the scope of the retries by calling RetrySynchronizationManager.getContext(). By default, the context is stored in a ThreadLocal. JEP 444 recommends that ThreadLocal should be avoided when using virtual threads, available in Java 21 and beyond. To store the contexts in a Map instead of a ThreadLocal, call RetrySynchronizationManager.setUseThreadLocal(false).

Using RecoveryCallback

When a retry is exhausted, the RetryOperations can pass control to a different callback: RecoveryCallback. To use this feature, clients can pass in the callbacks together to the same method, as the following example shows:

MyObject myObject = template.execute(new RetryCallback<MyObject, Exception>() {
    public MyObject doWithRetry(RetryContext context) {
        // business logic here
    },
  new RecoveryCallback<MyObject>() {
    MyObject recover(RetryContext context) throws Exception {
          // recover logic here
    }
});

If the business logic does not succeed before the template decides to abort, the client is given the chance to do some alternate processing through the recovery callback.

Stateless Retry

In the simplest case, a retry is just a while loop: the RetryTemplate can keep trying until it either succeeds or fails. The RetryContext contains some state to determine whether to retry or abort. However, this state is on the stack, and there is no need to store it anywhere globally. Consequently, we call this "stateless retry". The distinction between stateless and stateful retry is contained in the implementation of RetryPolicy (RetryTemplate can handle both). In a stateless retry, the callback is always executed in the same thread as when it failed on retry.

Stateful Retry

Where the failure has caused a transactional resource to become invalid, there are some special considerations. This does not apply to a simple remote call, because there is (usually) no transactional resource, but it does sometimes apply to a database update, especially when using Hibernate. In this case, it only makes sense to rethrow the exception that called the failure immediately so that the transaction can roll back and we can start a new (and valid) one.

In these cases, a stateless retry is not good enough, because the re-throw and roll back necessarily involve leaving the RetryOperations.execute() method and potentially losing the context that was on the stack. To avoid losing the context, we have to introduce a storage strategy to lift it off the stack and put it (at a minimum) in heap storage. For this purpose, Spring Retry provides a storage strategy called RetryContextCache, which you can inject into the RetryTemplate. The default implementation of the RetryContextCache is in-memory, using a simple Map. It has a strictly enforced maximum capacity, to avoid memory leaks, but it does not have any advanced cache features (such as time to live). You should consider injecting a Map that has those features if you need them. For advanced usage with multiple processes in a clustered environment, you might also consider implementing the RetryContextCache with a cluster cache of some sort (though, even in a clustered environment, this might be overkill).

Part of the responsibility of the RetryOperations is to recognize the failed operations when they come back in a new execution (and usually wrapped in a new transaction). To facilitate this, Spring Retry provides the RetryState abstraction. This works in conjunction with special execute methods in the RetryOperations.

The failed operations are recognized by identifying the state across multiple invocations of the retry. To identify the state, you can provide a RetryState object that is responsible for returning a unique key that identifies the item. The identifier is used as a key in the RetryContextCache.

Warning: Be very careful with the implementation of Object.equals() and Object.hashCode() in the key returned by RetryState. The best advice is to use a business key to identify the items. In the case of a JMS message, you can use the message ID.

When the retry is exhausted, you also have the option to handle the failed item in a different way, instead of calling the RetryCallback (which is now presumed to be likely to fail). As in the stateless case, this option is provided by the RecoveryCallback, which you can provide by passing it in to the execute method of RetryOperations.

The decision to retry or not is actually delegated to a regular RetryPolicy, so the usual concerns about limits and timeouts can be injected there (see the Additional Dependencies section).

Retry Policies

Inside a RetryTemplate, the decision to retry or fail in the execute method is determined by a RetryPolicy, which is also a factory for the RetryContext. The RetryTemplate is responsible for using the current policy to create a RetryContext and passing that in to the RetryCallback at every attempt. After a callback fails, the RetryTemplate has to make a call to the RetryPolicy to ask it to update its state (which is stored in RetryContext). It then asks the policy if another attempt can be made. If another attempt cannot be made (for example, because a limit has been reached or a timeout has been detected), the policy is also responsible for identifying the exhausted state -- but not for handling the exception. RetryTemplate throws the original exception, except in the stateful case, when no recovery is available. In that case, it throws RetryExhaustedException. You can also set a flag in the RetryTemplate to have it unconditionally throw the original exception from the callback (that is, from user code) instead.

Tip: Failures are inherently either retryable or not -- if the same exception is always going to be thrown from the business logic, it does not help to retry it. So you should not retry on all exception types. Rather, try to focus on only those exceptions that you expect to be retryable. It is not usually harmful to the business logic to retry more aggressively, but it is wasteful, because, if a failure is deterministic, time is spent retrying something that you know in advance is fatal.

Spring Retry provides some simple general-purpose implementations of stateless RetryPolicy (for example, a SimpleRetryPolicy) and the TimeoutRetryPolicy used in the preceding example.

The SimpleRetryPolicy allows a retry on any of a named list of exception types, up to a fixed number of times. The following example shows how to use it:

// Set the max attempts including the initial attempt before retrying
// and retry on all exceptions (this is the default):
SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true));

// Use the policy...
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(policy);
template.execute(new RetryCallback<MyObject, Exception>() {
    public MyObject doWithRetry(RetryContext context) {
        // business logic here
    }
});

A more flexible implementation called ExceptionClassifierRetryPolicy is also available. It lets you configure different retry behavior for an arbitrary set of exception types through the ExceptionClassifier abstraction. The policy works by calling on the classifier to convert an exception into a delegate RetryPolicy. For example, one exception type can be retried more times before failure than another, by mapping it to a different policy.

You might need to implement your own retry policies for more customized decisions. For instance, if there is a well-known, solution-specific, classification of exceptions into retryable and not retryable.

Backoff Policies

When retrying after a transient failure, it often helps to wait a bit before trying again, because (usually) the failure is caused by some problem that can be resolved only by waiting. If a RetryCallback fails, the RetryTemplate can pause execution according to the BackoffPolicy. The following listing shows the definition of the BackoffPolicy interface:

public interface BackoffPolicy {

    BackOffContext start(RetryContext context);

    void backOff(BackOffContext backOffContext)
        throws BackOffInterruptedException;

}

A BackoffPolicy is free to implement the backoff in any way it chooses. The policies provided by Spring Retry all use Object.wait(). A common use case is to back off with an exponentially increasing wait period, to avoid two retries getting into lock step and both failing (a lesson learned from Ethernet). For this purpose, Spring Retry provides ExponentialBackoffPolicy. Spring Retry also provides randomized versions of delay policies that are quite useful to avoid resonating between related failures in a complex system, by adding jitter.

Listeners

It is often useful to be able to receive additional callbacks for cross-cutting concerns across a number of different retries. For this purpose, Spring Retry provides the RetryListener interface. The RetryTemplate lets you register RetryListener instances, and they are given callbacks with the RetryContext and Throwable (where available during the iteration).

The following listing shows the RetryListener interface:

public interface RetryListener {

	default <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
		return true;
	}

	default <T, E extends Throwable> void onSuccess(RetryContext context, RetryCallback<T, E> callback, T result) {
	}

	default <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
			Throwable throwable) {
	}

	default <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
			Throwable throwable) {
	}

}

The open and close callbacks come before and after the entire retry in the simplest case, and onSuccess, onError apply to the individual RetryCallback calls; the current retry count can be obtained from the RetryContext. The close method might also receive a Throwable. Starting with version 2.0, the onSuccess method is called after a successful call to the callback. This allows the listener to examine the result and throw an exception if the result doesn't match some expected criteria. The type of the exception thrown is then used to determine whether the call should be retried or not, based on the retry policy. If there has been an error, it is the last one thrown by the RetryCallback.

Note that when there is more than one listener, they are in a list, so there is an order. In this case, open is called in the same order, while onSuccess, onError, and close are called in reverse order.

Listeners for Reflective Method Invocations

When dealing with methods that are annotated with @Retryable or with Spring AOP intercepted methods, Spring Retry allows a detailed inspection of the method invocation within the RetryListener implementation.

Such a scenario could be particularly useful when there is a need to monitor how often a certain method call has been retried and expose it with detailed tagging information (such as class name, method name, or even parameter values in some exotic cases).

Starting with version 2.0, the MethodInvocationRetryListenerSupport has a new method doOnSuccess.

The following example registers such a listener:

template.registerListener(new MethodInvocationRetryListenerSupport() {
      @Override
      protected <T, E extends Throwable> void doClose(RetryContext context,
          MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
        monitoringTags.put(labelTagName, callback.getLabel());
        Method method = callback.getInvocation()
            .getMethod();
        monitoringTags.put(classTagName,
            method.getDeclaringClass().getSimpleName());
        monitoringTags.put(methodTagName, method.getName());

        // register a monitoring counter with appropriate tags
        // ...

        @Override
        protected <T, E extends Throwable> void doOnSuccess(RetryContext context,
                MethodInvocationRetryCallback<T, E> callback, T result) {

            Object[] arguments = callback.getInvocation().getArguments();

            // decide whether the result for the given arguments should be accepted
            // or retried according to the retry policy
        }

      }
    });

Declarative Retry

Sometimes, you want to retry some business processing every time it happens. The classic example of this is the remote service call. Spring Retry provides an AOP interceptor that wraps a method call in a RetryOperations instance for exactly this purpose. The RetryOperationsInterceptor executes the intercepted method and retries on failure according to the RetryPolicy in the provided RepeatTemplate.

Java Configuration for Retry Proxies

You can add the @EnableRetry annotation to one of your @Configuration classes and use @Retryable on the methods (or on the type level for all methods) that you want to retry. You can also specify any number of retry listeners. The following example shows how to do so:

@Configuration
@EnableRetry
public class Application {

    @Bean
    public Service service() {
        return new Service();
    }

    @Bean public RetryListener retryListener1() {
        return new RetryListener() {...}
    }

    @Bean public RetryListener retryListener2() {
        return new RetryListener() {...}
    }

}

@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public service() {
        // ... do something
    }
}

You can use the attributes of @Retryable to control the RetryPolicy and BackoffPolicy, as follows:

@Service
class Service {
    @Retryable(maxAttempts=12, backoff=@Backoff(delay=100, maxDelay=500))
    public service() {
        // ... do something
    }
}

The preceding example creates a random backoff between 100 and 500 milliseconds and up to 12 attempts. There is also a stateful attribute (default: false) to control whether the retry is stateful or not. To use stateful retry, the intercepted method has to have arguments, since they are used to construct the cache key for the state.

The @EnableRetry annotation also looks for beans of type Sleeper and other strategies used in the RetryTemplate and interceptors to control the behavior of the retry at runtime.

The @EnableRetry annotation creates proxies for @Retryable beans, and the proxies (that is, the bean instances in the application) have the Retryable interface added to them. This is purely a marker interface, but it might be useful for other tools looking to apply retry advice (they should usually not bother if the bean already implements Retryable).

If you want to take an alternative code path when the retry is exhausted, you can supply a recovery method. Methods should be declared in the same class as the @Retryable instance and marked @Recover. The return type must match the @Retryable method. The arguments for the recovery method can optionally include the exception that was thrown and (optionally) the arguments passed to the original retryable method (or a partial list of them as long as none are omitted up to the last one needed). The following example shows how to do so:

@Service
class Service {
    @Retryable(retryFor = RemoteAccessException.class)
    public void service(String str1, String str2) {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e, String str1, String str2) {
       // ... error handling making use of original args if required
    }
}

To resolve conflicts between multiple methods that can be picked for recovery, you can explicitly specify recovery method name. The following example shows how to do so:

@Service
class Service {
    @Retryable(recover = "service1Recover", retryFor = RemoteAccessException.class)
    public void service1(String str1, String str2) {
        // ... do something
    }

    @Retryable(recover = "service2Recover", retryFor = RemoteAccessException.class)
    public void service2(String str1, String str2) {
        // ... do something
    }

    @Recover
    public void service1Recover(RemoteAccessException e, String str1, String str2) {
        // ... error handling making use of original args if required
    }

    @Recover
    public void service2Recover(RemoteAccessException e, String str1, String str2) {
        // ... error handling making use of original args if required
    }
}

Version 1.3.2 and later supports matching a parameterized (generic) return type to detect the correct recovery method:

@Service
class Service {

    @Retryable(retryFor = RemoteAccessException.class)
    public List<Thing1> service1(String str1, String str2) {
        // ... do something
    }

    @Retryable(retryFor = RemoteAccessException.class)
    public List<Thing2> service2(String str1, String str2) {
        // ... do something
    }

    @Recover
    public List<Thing1> recover1(RemoteAccessException e, String str1, String str2) {
       // ... error handling for service1
    }

    @Recover
    public List<Thing2> recover2(RemoteAccessException e, String str1, String str2) {
       // ... error handling for service2
    }

}

Version 1.2 introduced the ability to use expressions for certain properties. The following example show how to use expressions this way:

@Retryable(exceptionExpression="message.contains('this can be retried')")
public void service1() {
  ...
}

@Retryable(exceptionExpression="message.contains('this can be retried')")
public void service2() {
  ...
}

@Retryable(exceptionExpression="@exceptionChecker.shouldRetry(#root)",
    maxAttemptsExpression = "#{@integerFiveBean}",
  backoff = @Backoff(delayExpression = "#{1}", maxDelayExpression = "#{5}", multiplierExpression = "#{1.1}"))
public void service3() {
  ...
}

Since Spring Retry 1.2.5, for exceptionExpression, templated expressions (#{...}) are deprecated in favor of simple expression strings (message.contains('this can be retried')).

Expressions can contain property placeholders, such as #{${max.delay}} or #{@exceptionChecker.${retry.method}(#root)}. The following rules apply:

  • exceptionExpression is evaluated against the thrown exception as the #root object.
  • maxAttemptsExpression and the @BackOff expression attributes are evaluated once, during initialization. There is no root object for the evaluation but they can reference other beans in the context

Starting with version 2.0, expressions in @Retryable, @CircuitBreaker, and BackOff can be evaluated once, during application initialization, or at runtime. With earlier versions, evaluation was always performed during initialization (except for Retryable.exceptionExpression which is always evaluated at runtime). When evaluating at runtime, a root object containing the method arguments is passed to the evaluation context.

Note: The arguments are not available until the method has been called at least once; they will be null initially, which means, for example, you can't set the initial maxAttempts using an argument value, you can, however, change the maxAttempts after the first failure and before any retries are performed. Also, the arguments are only available when using stateless retry (which includes the @CircuitBreaker).

Version 2.0 adds more flexibility to exception classification.

@Retryable(retryFor = RuntimeException.class, noRetryFor = IllegalStateException.class, notRecoverable = {
        IllegalArgumentException.class, IllegalStateException.class })
public void service() {
    ...
}

@Recover
public void recover(Throwable cause) {
    ...
}

retryFor and noRetryFor are replacements of include and exclude properties, which are now deprecated. The new notRecoverable property allows the recovery method(s) to be skipped, even if one matches the exception type; the exception is thrown to the caller either after retries are exhausted, or immediately, if the exception is not retryable.

Examples
@Retryable(maxAttemptsExpression = "@runtimeConfigs.maxAttempts",
        backoff = @Backoff(delayExpression = "@runtimeConfigs.initial",
                maxDelayExpression = "@runtimeConfigs.max", multiplierExpression = "@runtimeConfigs.mult"))
public void service() {
    ...
}

Where runtimeConfigs is a bean with those properties.

@Retryable(maxAttemptsExpression = "args[0] == 'something' ? 3 : 1")
public void conditional(String string) {
    ...
}

Additional Dependencies

The declarative approach to applying retry handling by using the @Retryable annotation shown earlier has an additional runtime dependency on AOP classes that need to be declared in your project. If your application is implemented by using Spring Boot, this dependency is best resolved by using the Spring Boot starter for AOP. For example, for Gradle, add the following line to your build.gradle file:

    runtimeOnly 'org.springframework.boot:spring-boot-starter-aop'

For non-Boot apps, you need to declare a runtime dependency on the latest version of AspectJ's aspectjweaver module. For example, for Gradle, you should add the following line to your build.gradle file:

    runtimeOnly 'org.aspectj:aspectjweaver:1.9.20.1'

Further customizations

Starting from version 1.3.2 and later @Retryable annotation can be used in custom composed annotations to create your own annotations with predefined behaviour. For example if you discover you need two kinds of retry strategy, one for local services calls, and one for remote services calls, you could decide to create two custom annotations @LocalRetryable and @RemoteRetryable that differs in the retry strategy as well in the maximum number of retries.

To make custom annotation composition work properly you can use @AliasFor annotation, for example on the recover method, so that you can further extend the versatility of your custom annotations and allow the recover argument value to be picked up as if it was set on the recover method of the base @Retryable annotation.

Usage Example:

@Service
class Service {
    ...
    
    @LocalRetryable(include = TemporaryLocalException.class, recover = "service1Recovery")
    public List<Thing> service1(String str1, String str2){
        //... do something
    }
    
    public List<Thing> service1Recovery(TemporaryLocalException ex,String str1, String str2){
        //... Error handling for service1
    }
    ...
    
    @RemoteRetryable(include = TemporaryRemoteException.class, recover = "service2Recovery")
    public List<Thing> service2(String str1, String str2){
        //... do something
    }

    public List<Thing> service2Recovery(TemporaryRemoteException ex, String str1, String str2){
        //... Error handling for service2
    }
    ...
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Retryable(maxAttempts = "3", backoff = @Backoff(delay = "500", maxDelay = "2000", random = true)
)
public @interface LocalRetryable {
    
    @AliasFor(annotation = Retryable.class, attribute = "recover")
    String recover() default "";

    @AliasFor(annotation = Retryable.class, attribute = "value")
    Class<? extends Throwable>[] value() default {};

    @AliasFor(annotation = Retryable.class, attribute = "include")

    Class<? extends Throwable>[] include() default {};

    @AliasFor(annotation = Retryable.class, attribute = "exclude")
    Class<? extends Throwable>[] exclude() default {};

    @AliasFor(annotation = Retryable.class, attribute = "label")
    String label() default "";

}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Retryable(maxAttempts = "5", backoff = @Backoff(delay = "1000", maxDelay = "30000", multiplier = "1.2", random = true)
)
public @interface RemoteRetryable {
    
    @AliasFor(annotation = Retryable.class, attribute = "recover")
    String recover() default "";

    @AliasFor(annotation = Retryable.class, attribute = "value")
    Class<? extends Throwable>[] value() default {};

    @AliasFor(annotation = Retryable.class, attribute = "include")
    Class<? extends Throwable>[] include() default {};

    @AliasFor(annotation = Retryable.class, attribute = "exclude")
    Class<? extends Throwable>[] exclude() default {};

    @AliasFor(annotation = Retryable.class, attribute = "label")
    String label() default "";

}

XML Configuration

The following example of declarative iteration uses Spring AOP to repeat a service call to a method called remoteCall:

<aop:config>
    <aop:pointcut id="transactional"
        expression="execution(* com..*Service.remoteCall(..))" />
    <aop:advisor pointcut-ref="transactional"
        advice-ref="retryAdvice" order="-1"/>
</aop:config>

<bean id="retryAdvice"
    class="org.springframework.retry.interceptor.RetryOperationsInterceptor"/>

For more detail on how to configure AOP interceptors, see the Spring Framework Documentation.

The preceding example uses a default RetryTemplate inside the interceptor. To change the policies or listeners, you need only inject an instance of RetryTemplate into the interceptor.

Contributing

Spring Retry is released under the non-restrictive Apache 2.0 license and follows a very standard Github development process, using Github tracker for issues and merging pull requests into the main branch. If you want to contribute even something trivial, please do not hesitate, but do please follow the guidelines in the next paragraph.

Before we can accept a non-trivial patch or pull request, we need you to sign the contributor's agreement. Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team and be given the ability to merge pull requests.

Getting Support

Check out the Spring Retry tags on Stack Overflow. Commercial support is available too.

Code of Conduct

This project adheres to the Contributor Covenant. By participating, you are expected to uphold this code. Please report unacceptable behavior to [email protected].

spring-retry's People

Contributors

aahlenst avatar aldex32 avatar artembilan avatar ashamukov avatar dependabot[bot] avatar dsyer avatar esivakumar26 avatar garyrussell avatar gmedici avatar hotire avatar hpoettker avatar joshlong avatar maciekwiso avatar making avatar mariusneo avatar olegz avatar oscarcitoz avatar philwebb avatar plummb avatar quaff avatar snicoll avatar spring-builds avatar spring-operator avatar stepan-romankov avatar tinesoft avatar tomazfernandes avatar torbenmoeller avatar ttulka avatar xak2000 avatar zhangchaown avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spring-retry's Issues

Readme.md misleading about SimpleRetryPolicy

Readme.md says:

  SimpleRetryPolicy policy = new SimpleRetryPolicy();
  policy.setRetryableExceptions(new Class[] {Exception.class});

... but SimpleRetryPolicy does not have a setRetryableExceptions method.

You can pass Map<Class<? extends Throwable>, Boolean> retryableExceptions as a constructor parameter -- Readme should say this instead.

@Retryable on interface instead of implementation class

I had to put the @Retryable on the interface method instead of the implementation class in order to make it work, which is different how it is described in the documentation.

public interface DemoService {
    @Retryable(RemoteAccessException.class)
    void service();
}

Execution Scoped RetryPolicy

I have come across a use case where I need to create a different RetryPolicy for each RetryTemplate.execute call. In my use case I am making an HTTP call that I want to retry but the RetryPolicy for that execution is based on information from the HTTP request being made. Given that we could be making multiple HTTP requests at the same time using a simple RetryTemplate is out of question because in theory we could be switching the RetryPolicy before a single request gets a chance to finish. I am wondering if others might think it would be useful to support a RetryPolicy for a given execution in addition to a global RetryPolicy for the template itself?

It seems to be typo in the README.md

There is 'RepeatTemplate' in the end of line 216 in README.md
I could't found out the RepeatTemplate file in the repository.
I think It should be fixed "RetryTemplate" probably.

@Recover on interface instead of implementation class

I'm having a similar issue with @Recover that is similar to the linked issue #32. In order to make it work it has to be on the interface and not the implementing class.

I used the following class and test

public interface TestService {

    void doStuff();

    void recover();
}
@Named
public class TestServiceImpl implements TestService {

    @Retryable
    public void doStuff() {
    }

    @Recover
    public void recover() {
        System.out.println("recover");
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public static class RetryTests extends SpringRetryTest {

        @Inject
        private TestService testService;

        @Test
        public void testRecover() {
            testService.doStuff();
            verify(testService, times(3)).doStuff();
        }

        @After
        public void validate() {
            validateMockitoUsage();
        }

        @Configuration
        @EnableRetry
        public static class SpringConfig {

            @Bean
            public TestService testService() {
                TestService testService = mock(TestServiceImpl.class);
                Mockito.doThrow(new RuntimeException())
                        .doThrow(new RuntimeException())
                        .doThrow(new RuntimeException())
                        .when(testService).doStuff();
                return testService;
        }
    }
}

But if you move the @Recover to the interface it works.

Allow @Retryable to specify the RetryPolicies to use.

The @RetryPolicy allows to specify a couple of properties even through using SpEL expression. However recently we had the need of configuring a custom RetryPolicy however trying to configure that with @Retryable isn't possible. It would be nice if the @Retryable had a policies argument in which you could put the names, referring to the bean instances, of the RetryPolicy beans to use.

If they aren't necessarily need to be Spring Beans a policiesClasses could possible be added to.

@Retryable exclude option does not work as expected

I have a method that throws a user-defined exception for a particular circumstance but should not be the reason for a retry and was added to the exclude option value.

Another user-defined exception is added to the include option of @retryable and there is an accompanying @recover.

If the exception that is supposed to be excluded from retry is thrown, an ExhaustedRetryException is thrown with a message complaining about a missing @recover method (for the excluded exception). The count for the retry is 1 when this occurs.

The policyMap has the excluded exception in it with a value of false, but is not being properly handled.

The @retryable annotation also has a backoff option defined along with maxAttempts set to 5.

stateless retry always throws original exception on exhaustion

this is maybe more a question than a real issue but i would like to know what is the expected behavior.

the readme states that

Simple implementations will just throw RetryExhaustedException which will cause any enclosing transaction to be rolled back

but for a stateless retry, that is not the behavior i am seeing (always original exception thrown).

handleRetryExhausted is invoked correctly, with state being null (ok, i guess, because the retry is stateless). but then, it looks like always the original exception is thrown back to the user, never wrapped in an ExhaustedRetryException. that is because thrown() is not invoked but instead just throw wrapIfNecessary(context.getLastThrowable()); (https://github.com/spring-projects/spring-retry/blob/master/src/main/java/org/springframework/retry/support/RetryTemplate.java#L467)

thanks for clarification!

Retry interceptor bean doesn't override default max attempts

     @Bean
     public RetryOperationsInterceptor myInterceptor() {
        return RetryInterceptorBuilder.stateless().maxAttempts(4).backOffOptions(1000, 1.1,      5000).build();
     }
...
    @Retryable(interceptor="myInterceptor")
    get(String id);

The backOffOptions work as specified in the bean, but the max attempts is always 3.

@Retryable won't work as expected with exclude

expected: print 3 times "Execute"
actual: prints single time

My interpretation is that only when both exclude and include are missing it retries all exceptions. But this is not clearly document and a bit unexpected.


public class RetryTestIT extends IntegrationTest {
    @Autowired
    private MyService service;

    @Test
    public void test() {
        service.execute();
    }

}

@Service
class MyService {

    @Retryable(exclude = RestClientException.class)
    public void execute() {
        System.out.println("Execute");
        throw new IllegalArgumentException();
    }
}

Are the @Retryable annotation properties cached?

I'm using expression by declaring properties of the annotation. My example is given below:

@Retryable(maxAttemptsExpression = "#{@instanceCounter.getCount(#accountNumber)}", backoff = @Backoff(delay = 100, maxDelay = 500))
public MyModel getMyModel(String accountNumber){
......
return myModel;
}

I want, that each time the method getMyModel is called, the bean instanceCounter also should be called.
But there was different behavior than I expected. The bean instanceCounter was called just one time, when the getMyModel method was called first time. Other time when I call the method, the bean instanceCounter is not called.

Seems like the maxAttemptsExpression property was cached after first call. And after that, value of this property is always used from cache.

It's right? There is a way to set dynamic value to this property?

Synchronous first attempt, asynchronous retries

Here is my scenario:

I want my first operation to be synchronous & upon failures, my subsequent operations (retries) to be asynchronous. To do this, I put the first attempt outside of spring retry and then wrapped the rest in a RetryTemplate nested inside an asynchronous hystrix command. This is probably not an ideal use of hystrix, but it is working fine. However, now I am trying to implement an ExceptionClassifier, and I am having trouble determining the best way to skip my asynchronous code based on how the first exception is classified, because I'm not sure how to get ExceptionClassifierRetryPolicy.canRetry() to return false on the 1st call. Also, in my scenario, I don't really want to just create a new async thread just to drop it.

It looks like I may have to call my classifier directly, get back a RetryPolicy and try to initialize it so that I can call canRetry directly before I hit async-land so I can know whether to skip retry. Then if not skipping I guess I would try and clear the context (maybe?) afterwards, so it doesn't screw up the normal RetryTemplate process. Would love to know if there is a better way. I think all of this would be solved if there was a way to do a synchronous first attempt and then asynchronously retry.

On another note, thanks for the project!. It is neat and I've enjoyed trying to figure out how the code works. Learned about volatile, soft references, and ThreadLocal today. :)

If this wasn't coherently stated enough, let me know and I'll clarify. I'm a little brain-damaged at the moment.

Comparing with com.github.rholder:guava-retrying - missing TimeLimiter

Hi @garyrussell ,

Thanks again for supporting this project, hope you're fine. I have a question/suggestion.

As of now I'd like to use Spring (instead of other alternatives) in all the supported cases - just to prevent mess with different dependencies, conflicts and so on. Spring Retry is great component, but before I've started using it, I've used another good project - Guava Retrying:
https://github.com/rholder/guava-retrying

In most cases it's very similar to Spring Retry and I'd like to focus on using the latter one. But there is a small use case which I miss, let me explain it to you.

I mostly use retry functionality for handling I/O errors. Including the database querying. And we have some sophisticated network issue when database connections are dropped externally while executing a complex (long-running) SQL query and no timeout can drop this connection afterwards - the thread just hangs infinitely. After long and painful troubleshooting I've invented a solution which works for us:

  1. Retry database querying in case of exceptions (up to 3 times).
  2. Configure execution timeout for database querying (up to 5 minutes).
    So no, if connection is silently dropped, the timeout exception is thrown and retried.

Using the above mentioned Guava Retrying project I'm able to configure both timeout and retry in one place:

    @Bean
    public Retryer<ResponseEntity<String>> guavaRetryer() {
        return RetryerBuilder.<ResponseEntity<String>>newBuilder()
                .retryIfException()
                .withWaitStrategy(WaitStrategies.exponentialWait(config.getRetryWaitMultiplier(), config.getRetryWaitMaxTime(), TimeUnit.MILLISECONDS))
                .withStopStrategy(StopStrategies.stopAfterAttempt(config.getRetryMaxCount()))
                .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(config.getRetryTimeout(), TimeUnit.MILLISECONDS))
                .withRetryListener(new RetryListener() {
                    public <V> void onRetry(Attempt<V> attempt) {
                        if (attempt.hasException()) {
                            logger.warn("Attempt #{}, exception occurred: {}", attempt.getAttemptNumber(), attempt.getExceptionCause().getMessage());
                        }
                    }
                })
                .build();
    }

This line does the timeout:

                .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(config.getRetryTimeout(), TimeUnit.MILLISECONDS))

But unfortunately such a functionality is missing in Spring Retry (or I have not found it). What do you think about supporting this? Or would you recommend any other alternative?

P.S.: I've tried configuring transaction timeouts with Spring Transactions, but this did not help - for example, in some cases the "hanging" occurred even before the transaction itself was initiated. Also it's just feels better to have such an external limiter (such a "backup plan") anyway.

Missing aspectjweaver dependency

Good evening, gentlemen.

Correct me if I'm wrong, but seems that "aspectjweaver" is a mandatory dependency to use spring-retry with annotations.

But when I generated project with spring-retry at "start.spring.io", this dependency was not included. In my opinion it's confusing - annotations are are one of the most common instruments in Spring, so it's seems natural for me to use them.

exception type should be parametric

public final <T> T execute(RetryCallback<T> retryCallback) throws Exception

should be changed to

public final <T, E extends Exception> T execute(RetryCallback<T, E> retryCallback) throws E

This would allow to use RuntimeException as type argument and so not forcing checked exceptions to be handled.

It won't retry when a retryable method was invoked by a @EventListener anotated method

Hi, Spring retry is very convenient but it seems not work when a retryable method was invoked by a @EventListener annotated method. Why?

public class TestEvent {

}

@Service
public class AnotherService {

  public void perform() {
    throw new RuntimeException();

  }
}


@Service
public class ActionService {

  @Autowired
  AnotherService anotherService;

  @Retryable(Exception.class)
  public void test() {
    anotherService.perform();
  }

  @EventListener
  public void handleEvent(TestEvent event){
    test();
  }
}

1.1.2 Dependencies not complete.

I've included 1.1.2 into a gradle project using this:

compile "org.springframework.retry:spring-retry:1.1.2.RELEASE"

However when I attempted to run the war file I get this exception:

Caused by: java.lang.ClassNotFoundException: org.aspectj.lang.annotation.Around
at java.net.URLClassLoader$1.run(URLClassLoader.java:366) ~[na:1.7.0_45]
....

It appears that there are dependencies which have not been resolved or downloaded. Is there something missing from the POM?

stateful @Retryable doesn't retry

@Component
public class TransferService {

    private AtomicInteger count = new AtomicInteger(0);

    @Retryable(include = ConcurrencyFailureException.class, stateful = true)
    @Transactional
    public void transfer(String fromAccountNo, String toAccountNo,BigDecimal amount) {
        // do transaction
        int i = count.addAndGet(1);
        if (i % 3 != 0) // rollback
            throw new OptimisticLockingFailureException("data already changed");
    }

}

it only attempt once and throw exception, I expect attempt three times and doesn't throw exception.

add functionality to annotation to allow a spel expressions

It would be very useful if the @retryable annotation allowed an expression using spel to have an easy way to further inspect an exception in order to determine if a retry should occur.

For example:

@Retryable(include=HttpClientErrorException.class, retryOn="#result.getHttpStatus().value() == 401")
public void runExchange() { ... }

Non-blocking backOff policy

Would like to have a backOff policy that does not block the thread of execution. The use-case for this is roughly having a long-lived job that is comprised of many short-lived steps, where some step in the sequence retries potentially many times for a long period. Freeing up the thread will allow steps from other jobs to be executed during the blocking step's backOff period.

RetryContext needs to implement Serializable if it is stored in a distributed cache

Hi. The Spring Batch MapRetryContextCache is not cluster aware and the Spring Batch Retry chapter suggests using a distributed cache.
See: http://docs.spring.io/spring-batch/reference/html/retry.html

I can do this only if the map value is serializable. This requires RetryContext and its fields (including implementations of RetryPolicy) to implement java.io.Serializable.

The ability for Spring Retry to be cluster aware is important and this would be a change much appreciated by many.
FYI: http://blog.ogorkis.net/2014/12/spring-amqp-statefulretryoperationsinte.html

Thanks!

Spring Retry + Hystrix issue

I have a @HystrixCommand annotated method that makes a restTemplate call. I also have another one that makes a SOAP call. When an exception is thrown inside the hystrixcommand annotated method, it returns a HystrixRuntimeException, and nested in that exception is the actual exception that was thrown, for instance httpClientErrorException, socketTimeout, or SoapFault.

I call these hystrixCommands from within a Spring RetryTemplate, but the problem is that I am having trouble make the RetryTemplate only retry on certain exceptions that are the nested exceptions within the HystrixRuntimeException.

The scenario now is such that, Spring retryTemplate will only retry if there is a HystrixRuntime Exception, I want to retry based on the excpetions nested within the hystrixRuntime Exception.

AccountClient.java

@Service
public class AccountClient {

    @Inject
    private Properties properties;

    @Inject
    private RestTemplate restTemplate;

    @HystrixCommand()
    public Account getAccount(String accountId){
        String url = properties.getAccountBaseUrl() + properties.getGetAccountEndpoint() + "accountId={accountId}";
        ResponseEntity<Account> response = restTemplate.getForEntity(url, Account.class, accountId);
        return response.getBody();
    }

}

AccountService.java

@Service
public class AccountService {

    @Inject
    private AccountClient accountClient;

    @Inject
    private RetryTemplate retryTemplate;

    private static final Logger log = LoggerFactory.getLogger(AccountService.class);

    public Account getAccount(String accountId){
        Account account = retryTemplate.execute(new RetryCallback<Account, RuntimeException>() {
            public Account doWithRetry(RetryContext context) {
                log.info("Retry Attempt: " + context.getRetryCount() + " Exception thrown: " + context.getLastThrowable());
                Account account = accountClient.getAccount(accountId);
                return account;
            }
        });
        return account;
    }
}

RetryTemplate bean in @configuration class

@Bean
    public RetryTemplate retryTemplate() {
        Map<Class<? extends Throwable>, Boolean> includeExceptions = new HashMap<>();
        String[] exceptions = properties.getExceptions().split(" ");
        List<String> exceptionz = Arrays.asList(exceptions);
        for (String e : exceptionz) {
            Class<Exception> exc = null;
            try {
                exc = (Class<Exception>) Class.forName(e);
            } catch (ClassNotFoundException ex) {
                // ex.printStackTrace(); TODO log here
            }
            includeExceptions.put(exc, true);
        }
        SimpleRetryPolicy policy = new SimpleRetryPolicy(properties.getMaxRetryAttempts(), includeExceptions);
        RetryTemplate template = new RetryTemplate();
        template.setRetryPolicy(policy);
        return template;
    }

Retry cache is populated before the context is updated

When serializing map values in a RetryContextCache, it appears the cache is populated before the context is updated.

This was seen during testing as part of: #62

I have attached the test and an implementation of RetryContextCache that serializes the map values. I have also added some logging which shows that when using the org.springframework.retry.policy.MapRetryContextCache there may be in-place update to the map value.
retry-test.zip

Release plan

Is it already known when version 1.2.0 is to be released? We would like to use autowired RetryListener.
Without having this feature we will have to provide our own annotations.

Asynchronous retries support

Hello

Is there any plans to support asynchronous operations? We are using SpringRetry but it's not applicable to this case at all, due to blocking nature. Would be really great to have something like

//service
CompletableFuture<Data> loadUnreliableData()

//retry block
AsyncRetryOperations retry = <magic here>
retry.execute(service::loadUnrealiableData).thenApply(data -> println("At last data is loaded!"))

I found some project targeting this problem: https://github.com/nurkiewicz/async-retry, but would be great to have this in spring retry to avoid working with multiple similiar libraries.

Why isn't SpringRetry reactive?

Hi!

I was wondering why is SpringRetry always blocking? Will there be a chance of making it reactive? Or am I missing something in the documentation and SpringRetry is actually nonblocking?

SimpleRetyPolicy JavaDocs

Change the javadocs/parameter name to clarify that this represents the total number of attempts not "retry" attempts (i.e. should be >= 1).

ref: http://stackoverflow.com/questions/27658033/spring-amqp-messagepropertiesall-headers-were-removed-during-deadlettering/27658335#comment43732871_27658335

/**
 * Setter for retry attempts.
 *
 * @param retryAttempts the number of attempts before a retry becomes
 * impossible.
 */
public void setMaxAttempts(int retryAttempts) {
    this.maxAttempts = retryAttempts;
}

/**
 * The maximum number of retry attempts before failure.
 *
 * @return the maximum number of attempts
 */
public int getMaxAttempts() {
    return maxAttempts;
}

Too many files open error due to connections in close_wait state while handling a usecase processing lots of data

Too many files open error due to connections in close_wait state while handling a usecase processing lots of data. Any idea?

fetchData method gets called 10,000 times sequentially in some cases and got too many files open state due to close wait connections exceed the system limit for total open files.

Any idea why RetryTemplate not closing the connection properly here?
Should i set connection, close header parameter to avoid this case?
Do we have any connection pooling mechanism available in RetryTemplate or RestTemplate?

public Info[] fetchData(Integer rId) throws Exception {
	Info[] Info = null;
	if (rId != null) {
		try {
			HttpHeaders headers = new HttpHeaders();
			headers.setAll(reqHelper.getAllJsonBasedRequestHeaders());
			HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
			ResponseEntity<Info[]> response = getRetryTemplate()
					.execute(new RetryCallback<ResponseEntity<Info[]>, Exception>() {
						@Override
						public ResponseEntity<Info[]> doWithRetry(RetryContext context) throws Exception {
							return getRestTemplate().exchange(url),
									HttpMethod.GET, entity, Info[].class);
						}
					});
			if (response.getStatusCode() == HttpStatus.OK) {
				Info = response.getBody();
			}
		} catch (Exception e) {

			throw e;
		}
	}
	return Info;
}



private RetryTemplate getRetryTemplate() {
	FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
	backOffPolicy.setBackOffPeriod(serviceConfig.getTimeToBackOff());
	RetryTemplate template = new RetryTemplate();
	template.setRetryPolicy(apiRetryPolicy);
	template.setBackOffPolicy(backOffPolicy);
	return template;
}




apiRetryPolicy:

public void init()
{
    final SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
    simpleRetryPolicy.setMaxAttempts(radarServiceProperties.getMaxRetryAttempts());
    this.setExceptionClassifier( new Classifier<Throwable, RetryPolicy>()
    {
        @Override
        public RetryPolicy classify( Throwable classifiable )
        {
                if ( classifiable instanceof ConnectException 
                		|| classifiable instanceof NullPointerException 
                		|| classifiable instanceof UnknownHostException)
                {
                        return new NeverRetryPolicy();
                }
                
                if(classifiable instanceof HttpStatusCodeException )
                {
                	HttpStatus errorCode = ((HttpStatusCodeException) classifiable).getStatusCode();
                	if(errorCode == HttpStatus.UNAUTHORIZED)
                	{
                		 return new NeverRetryPolicy();
                	}
                }

                return simpleRetryPolicy;
            }
        } );
    }

}

Make Retry and Circuit Breaker composable

I want to achieve following behaviour: Specific execution failures are first retried according to e.g. a SimpleRetryPolicy, then if the policy is exceeded the failure is recorded by the CircuitBreaker.

Although both SimpleRetryPolicy and CircuitBreakerPolicy implement RetryPolicy and can be used to forge a CompositeRetryPolicy, this seems not to be an intended use-case for Spring Retry.

It feels like the reason for this is that stateful (CircuitBreaker) and stateless retries are not (yet?) composable due to some shared global state. Or am I missing something?

Update documentation to make it clear that original arguments can be passed to recover method

It will help if the documentation says clearly (via an example) that the following is supported:

@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public void service(String str1, String str2) {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e, String str1, String str2) {
       // ... error handling making use of original args if required
    }
}

Not implemented EQUALS methods cause OOM while using AOP

AOP functionality creates classes dynamically if needed class has not exist yet. Equals method is called while checking if class has exist. In my case method equals in org.springframework.aop.framework.CglibAopProxy$ProxyCallbackFilter always returns false because of this code:
if (!equalsPointcuts(thisAdvisor, thatAdvisor)) { return false; }
Because of there is no overriden EQUALS in org.springframework.retry.annotation.RetryConfiguration$AnnotationClassOrMethodPointcut.
Class loader stores all classes which it has loaded. So I get OOM because of many similar classes are stored in class loader (screen of heapdump is in https://jira.spring.io/browse/SPR-14738)

Key generation for method interceptors is not really unique enough

Things work out most of the time because the default is to have a different local context cache for each interceptor (each @Retryable). But if the context cache is shared, we need to make the cache key more unique (it should include the method signature or label as well as the arguments).

Rework Exception-wrapping

Currently, @CircuitBreaker always wraps failures into into an ExhaustedRetryException. This hides the true nature of the failure and makes it furthermore impossible to figure out whether the circuit breaker is actually open or not.

Having e.g. a @Recover-method just to unwrap and rethrow exceptions seems not a proper way to go... it also doesn't solve the circuit breaker status issue.

The only currently available alternative is to implement a custom circuit breaker RetryTemplate which feels odd.

Please fix.

how can I retry private method?

i want to:

userService.getUser(1L);

not working:

debug :
line 126: //AnnotationAwareRetryOperationsInterceptor.getDelegate()
param: method = getUser not fetchUserInfo
so retryable = null

@Service
public class UserService {
    pubilc getUser(Long id) { 
        this.fetchUserInfo(id);
    }

   @Retryable(NetworkIOException.class)
   private UserInfo fetchUserInfo(Long id) {
       //client.get(http://xxxx)
   }
}

working:

but this will query database three times

@Service
public class UserService {

   @Retryable(NetworkIOException.class)
    pubilc getUser(Long id) { 
        Strign serial = dal.getSerialById;
        this.fetchUserInfo(serial);
    }

   private UserInfo fetchUserInfo(String serial) {
       //client.get(http://xxxx/:serial)
   }
}

RetryListener can't easily determine if the retry will be retried or not

I want to use the retryListener to be able to log WARNs when I do the retry and ERRORs when I am going to eventually rethrow it. I'm using spring-retry to manage the "exception policy" for my web service so it sits at the uppermost layer of my software. Thus I want to handle exception logging at this layer. Unfortunately I cannot easily determine inside the onError method whether or not the given throwable will be retried or not. Sure I can get the current attempt count from the context, and if my listener has a reference to the retry policy I could call canRetry -- but that doesn't help because at the point when RetryTemplate calls onError -- it hasn't yet caled registerThrowable.

This seems like a pretty reasonable feature that should be supported. A simple, backwards compatible way would just be to registerThrowable before calling onError. I think that makes the context.getLastThrown make more sense anyways from the perspective of the listener. Or there's of course more appropriate but less backwards compatible ways.

If you can't see a problem with changing the order to call registerThrowable before onError - I can submit a PR for that today.

Use RetryListener with @Retryable

Hi,

I'd like to be able to register RetryListener for my RetryOperationsInterceptor, which I use as a configuration for Retryable annotation.

But as I see in the docs, such option is available only for RetryTemplate.

Am I correct? Any plans to support Retryable as well?

Is there a way to use @Retryable with XML configuration?

I have a very large application that uses XML configuration throughout. To some of the operations we would like to add the @Retryable annotation so that these can be retried a few times before giving up fully.

The documentation suggests that @Retryable is activated using @EnableRetry on a Java configuration class. Since we do not use Java based configuration, is there a way to enable @Retryable using XML configuration?

possibile wrong exception detection

Hello,
in RetryTemplate class, wrapIfNecessary method try to recognize if throwable arg is a checked or unchecked exception but in the second conditional statement (line 526) the instanceof check for an Exception instead of RuntimeException. Is it correct?

spring amqp enable retry by configuration and prevent it according to a specified exception

I have the following two cases
1- In case of ExceptionA : retrying for finite number of times and finally when number of retrials exhausted, message is written in a dead letter queue
2- In case of ExceptionB : simply, message should be written to dead letter queue

I want to support the two cases on the same listener container factory and the same queue.

I already have the following configuration to support case 1 successfully

@Bean
    public RetryOperationsInterceptor workMessagesRetryInterceptor() {
        return RetryInterceptorBuilder.stateless()
                .maxAttempts(5)
                .backOffOptions(1000, 2, 10000)
                .recoverer(new RejectAndDontRequeueRecoverer())
                .build();
    }
    @Bean
    public SimpleRabbitListenerContainerFactory myRabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
      SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
      factory.setConnectionFactory(connectionFactory);
      factory.setMaxConcurrentConsumers(8);
      factory.setAdviceChain(workMessagesRetryInterceptor());


      return factory;
    }

Now I want pretty urgently to extend the previous configuration to support case 2 too.

Thanks in advance,

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.