I have a Spring Boot (2.6.6) @Scheduled
method, which fetches data from an external service every 10 minutes.
Under normal condition, I am happy with the interval. However I'd like to shorten it on a one-time basis if the data fetch fails (the service is temporarily not available) and force the method to be scheduled earlier.
Something like this:
@Scheduled(fixedDelay = 10, timeUnit = TimeUnit.MINUTES)
public void fetchData() {
try {
this.data = myServiceConnector.fetchData();
}
catch (MyServiceNotAvailableException ex) {
// temporarily set the scheduling delay so it will happen again in 5 seconds - HOWTO?
}
}
N.B. If it is relevant for the problem, the act of data fetching is a reactive code, so in fact it looks like
@Scheduled(fixedDelay = 10, timeUnit = TimeUnit.MINUTES)
public void fetchData() {
myServiceConnector.fetchData()
.doOnSuccess(fetchedData -> this.data = fetchedData)
.doOnError(throwable ->
// temporarily set the scheduling delay so it will happen again in 5 seconds - HOWTO?
)
.subscribeOn(Schedulers.boundedElastic())
.subscribe();
}
CodePudding user response:
Edit: Updated to show use of always AlwaysRetryPolicy (retry until successful).
Seems like what you really need is just retry logic integrated into your @Scheduled
method. Spring Retry is a Spring AOP project that could help you here. You can configure things like a number of retries, and a delay to wait before retrying. Broadly it would look like this (there is additional configuration necessary to enable this):
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
AlwaysRetryPolicy policy = new AlwaysRetryPolicy();
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
...
@Scheduled(fixedDelay = 10, timeUnit = TimeUnit.MINUTES)
public void fetchData() {
retryTemplate.execute(context -> {
this.data = myServiceConnector.fetchData();
});
}
The project has more information on Github and here is a guide from Baeldung.
CodePudding user response:
I have come with the following solution.
- At the beginning, I manually schedule the
fetchData()
task to run immediately. - In the subscription time of the
fetchData()
Mono
, based on the result of the fetch operation, I manually schedule the next run. As this happens at the end of the operation, it is more or less end-to-start interval.
@Service
@Slf4j
@RequiredArgsConstructor
public class DataUpdaterService {
@Autowired
private MyServiceConnector myServiceConnector;
private TaskScheduler scheduler;
private final Clock clock = Clock.systemUTC();
@Getter
private volatile Data data = null;
@PostConstruct
void init() {
final ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor();
scheduler = new ConcurrentTaskScheduler(localExecutor);
// Schedule the task to run for the first time right now:
scheduleFetchTask(Duration.ZERO);
}
private void scheduleFetchTask(Duration interval) {
scheduler.schedule(this::fetchData, clock.instant().plus(interval));
}
private void fetchData() {
myServiceConnector.fetchData()
.doOnSuccess(fetchedData -> {
this.data = fetchedData;
// Schedule the task in the normal mode:
scheduleFetchTask(Duration.ofMinutes(10));
})
.doOnError(throwable -> {
log.error("Error fetching data from myService", throwable);
// Schedule the task in the retry mode:
scheduleFetchTask(Duration.ofSeconds(3));
})
.subscribeOn(Schedulers.boundedElastic())
.subscribe();
}
}
The signature of the myServiceConnector.fetchData()
is
Mono<Data> fetchData()