Home > Enterprise >  Force Spring Boot @Scheduled method to be called "out of schedule" in case of failure
Force Spring Boot @Scheduled method to be called "out of schedule" in case of failure

Time:04-06

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.

  1. At the beginning, I manually schedule the fetchData() task to run immediately.
  2. 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()
  • Related