Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combining @Retryable and @Scheduled cause scheduled execution problems #94

Closed
selimok opened this issue Nov 13, 2017 · 9 comments
Closed

Comments

@selimok
Copy link

selimok commented Nov 13, 2017

Hi,

if I use @retryable annotation for a method of a certain bean and @scheduled for another method of the same bean, ScheduledAnnotationBeanPostProcessor doesn't process this class and thus @scheduled method is not executed. This is a very strange problem.

If I remove the @retryable annotation, ScheduledAnnotationBeanPostProcessor process the bean with processScheduled() method and the scheduled task is executed without any problem.

I don't really understand which part of the framework (scheduling or retry) is responsible for this problem.

Thanks.

@garyrussell
Copy link
Contributor

What Spring Framework and Spring Retry versions are you using? It works fine for me with boot 1.5.8 (spring 4.3.12 and retry 1.2.1).

It works with both CGLIB and JDK proxies too; the latter works with the annotation on either the bean or the interface).

JDK:

@SpringBootApplication
@EnableScheduling
@EnableRetry
public class RetryGh94Application {

	public static void main(String[] args) {
		SpringApplication.run(RetryGh94Application.class, args)
			.close();
	}

	@Bean
	public ApplicationRunner runner(MyBean bean) throws Exception {
		System.out.println(bean.getClass());
		return args -> {
			try {
				bean.retry();
			}
			catch (Exception e) {
				//
			}
			Thread.sleep(30_000);
		};
	}

	@Bean
	public MyBean bean() {
		return new MyBeanImpl();
	}

	public interface MyBean {

//		@Scheduled(fixedDelay = 5000)
		void sched();

//		@Retryable(maxAttempts = 3)
		void retry();

	}

	public static class MyBeanImpl implements MyBean {

		@Override
		@Scheduled(fixedDelay = 5000)
		public void sched() {
			System.out.println("sched");
		}

		@Override
		@Retryable(maxAttempts = 3)
		public void retry() {
			System.out.println("retry");
			throw new RuntimeException("foo");
		}

	}

}

CGLIB:

@SpringBootApplication
@EnableScheduling
@EnableRetry
public class RetryGh94Application {

	public static void main(String[] args) {
		SpringApplication.run(RetryGh94Application.class, args)
			.close();
	}

	@Bean
	public ApplicationRunner runner(MyBean bean) throws Exception {
		System.out.println(bean.getClass());
		return args -> {
			try {
				bean.retry();
			}
			catch (Exception e) {
				//
			}
			Thread.sleep(30_000);
		};
	}

	@Bean
	public MyBean bean() {
		return new MyBean();
	}

	public static class MyBean {

		@Scheduled(fixedDelay = 5000)
		public void sched() {
			System.out.println("sched");
		}

		@Retryable(maxAttempts = 3)
		public void retry() {
			System.out.println("retry");
			throw new RuntimeException("foo");
		}

	}

}

@selimok
Copy link
Author

selimok commented Nov 13, 2017

Strange. I'm using spring-retry 1.2.1.RELEASE and spring-boot 1.5.5-RELEASE. I will try to use 1.5.8 and maybe in a freshly created new project to eliminate potential side effects and give you feedback.

Thank you for your quick response.

@selimok
Copy link
Author

selimok commented Nov 13, 2017

The same project with spring-boot 1.5.8 has the same problem, as long as I remove the @retryable annotation it starts to work. I will create a fresh project and evolve it to my original project until I find the root cause. It will take some time, but I will come back 😄

@garyrussell
Copy link
Contributor

garyrussell commented Nov 13, 2017

I was wondering if it has something to do with the order in which the BPPs are run.

I got the reverse problem (sched but no retry) when the bean was already a proxy and was forced to put the annotations on the interface (JDK proxy) to make it work...

@SpringBootApplication
@EnableRetry
@EnableScheduling
public class RetryGh94Application {

	public static void main(String[] args) {
		SpringApplication.run(RetryGh94Application.class, args)
			.close();
	}

	@Bean
	public ApplicationRunner runner(MyBean bean) throws Exception {
		System.out.println(bean.getClass());
		return args -> {
			try {
				bean.retry();
			}
			catch (Exception e) {
				//
			}
			Thread.sleep(30_000);
		};
	}

	@Bean
	public MyBean bean() {
		MyBeanImpl myBeanImpl = new MyBeanImpl();
		ProxyFactory factory = new ProxyFactory(myBeanImpl);
		return (MyBean) factory.getProxy();
	}

	public interface MyBean {

		@Scheduled(fixedDelay = 5000)
		void sched();

		@Retryable(maxAttempts = 3)
		void retry();

	}

	public static class MyBeanImpl implements MyBean {

		@Override
		@Scheduled(fixedDelay = 5000)
		public void sched() {
			System.out.println("sched");
		}

		@Override
		@Retryable(maxAttempts = 3)
		public void retry() {
			System.out.println("retry");
			throw new RuntimeException("foo");
		}

	}

}

I was able to reproduce your condition (retry but no sched) with CGLIB proxies...

@SpringBootApplication
@EnableRetry(proxyTargetClass = true)
@EnableScheduling
public class RetryGh94Application {

	public static void main(String[] args) {
		SpringApplication.run(RetryGh94Application.class, args)
			.close();
	}

	@Bean
	public ApplicationRunner runner(MyBean bean) throws Exception {
		System.out.println(bean.getClass());
		return args -> {
			try {
				bean.retry();
			}
			catch (Exception e) {
				//
			}
			Thread.sleep(30_000);
		};
	}

	@Bean
	public MyBean bean() {
		MyBean myBeanImpl = new MyBean();
		ProxyFactory factory = new ProxyFactory(myBeanImpl);
		factory.setProxyTargetClass(true);
		return (MyBean) factory.getProxy();
	}

	public static class MyBean {

		@Scheduled(fixedDelay = 5000)
		public void sched() {
			System.out.println("sched");
		}

		@Retryable(maxAttempts = 3)
		public void retry() {
			System.out.println("retry");
			throw new RuntimeException("foo");
		}

	}

}

So it looks like it might be an issue with the ScheduledAnnotationBeanPostProcessor.
You might want to open a JIRA Issue against SPR and reference this issue.

A work around should be possible if you add an interface and use JDK proxies.

@selimok
Copy link
Author

selimok commented Nov 14, 2017

Thank you for your comprehensive response. I just created a ticket in JIRA. Here is the link: https://jira.spring.io/browse/SPR-16196

@selimok
Copy link
Author

selimok commented Jan 16, 2018

Hi @garyrussell, you can mark this issue as resolved since SPR-16196 is fixed now.

Thank you.

@ankitspring
Copy link

Hi @garyrussell , I am facing a similar issue where I have used @scheduled with cron expression to schedule my job to run once a day at a particular time. I need to retry it in case of an exception which I configured using @retryable. This is my first time using @retryable annotation. I took help from http://www.baeldung.com/spring-retry.

But it just won't retry the job after 5000 milliseconds which I configured using delay parm. Is it possible to use both of these annotations for same method? The only difference between this post and my implementation is that I am trying to use it for a method while this post is using two.

Please let me know if you need to take a look at sample code and I will make some. Thank you!

@garyrussell
Copy link
Contributor

Please don't piggy-back on existing (closed) issues.

Are you using Spring 5.0.3 or higher (or 4.3.14 or higher) as noted as the fix versions on SPR-16196.

The current version of spring-retry is 1.2.2.RELEASE.

I just tested with this and it worked just fine...

@SpringBootApplication
@EnableRetry
@EnableScheduling
public class Gitter27Application {

	public static void main(String[] args) {
		SpringApplication.run(Gitter27Application.class, args);
	}

	@Retryable
	@Scheduled(fixedDelay = 5000)
	public void Foo() {
		System.out.println("here");
		throw new RuntimeException("foo");
	}

}

@amolkumar18
Copy link

Using @retryable in @service at method level which is going to retry 3 times by default if get any exception and @recover responsible to sa error count in postgres database table.
I have seperate class for @EnableScheduling with scheduled cron run every four hours which will going to process these failed cases by querying this postgres table.
When aplication starts and failed for the service class within the method annotated with @retryable able to save record in db but when trying through scheduler it's not updating the failure count in database

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants