Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how can you configure spring to execute overlapping fixedRate tasks?

i am trying to execute a task at a fixed rate using the @Scheduled annotation in java spring. however, it seems that by default spring will not execute a fixedRate task at a fixed rate if the task is slower than the rate. is there some setting i can add to my spring configuration to change this behavior?

example:

@Service
public class MyTask{
    @Scheduled(fixedRate = 1000)
    public void doIt(){
        // this sometimes takes >1000ms, in which case the next execution is late
        ...
    }
}

i have a work-around, but it seems less than ideal. basically, i just replace the default single-thread executor with a thread pool, then i have a scheduled method call an async method since the @Async annotation allows concurrent executions:

@Service
public class MyTask{
    @Async
    public void doIt(){
        // this sometimes takes >1000ms, but the next execution is on time
        ...
    }
}

@Service
public class MyTaskScheduler{
    ...
    @Scheduled(fixedRate = 1000)
    public void doIt(){
        myTask.doIt();
    }
}

@Configuration
@EnableScheduling
@EnableAsync
public class MySpringJavaConfig{
    @Bean(destroyMethod = "shutdown")
    public Executor taskScheduler() {
        return Executors.newScheduledThreadPool(5);
    }
}

boring details of my real-world scenario: in my production code i have a task that takes between 10ms and 10 minutes depending on the current workload. ideally, i would like to capture a new thread from the pool every 1000ms so that the number of concurrent threads increases with the workload. obviously i have an upper limit on threads in place (among other controls) to keep things from getting out of hand.

like image 855
james turner Avatar asked May 18 '15 21:05

james turner


People also ask

How can spring overlap schedules be prevented?

by default, spring uses a single-threaded Executor. so no two @Scheduled tasks will ever overlap. even two @Scheduled methods in completely unrelated classes will not overlap simply because there is only a single thread to execute all @Scheduled tasks.

How do I run multiple scheduler in spring boot?

We can easily schedule tasks in spring boot by using @Scheduled annotation. And then we need to enable scheduling by adding @EnableScheduling annotation to a spring configuration class. Spring uses ThreadPoolTaskScheduler for scheduled tasks, which internally delegates to a ScheduledExecutorService.

What is @scheduled in spring?

We can turn any method in a Spring bean for scheduling by adding the @Scheduled annotation to it. The @Scheduled is a method-level annotation applied at runtime to mark the method to be scheduled. It takes one attribute from cron , fixedDelay , or fixedRate for specifying the schedule of execution in different formats.


2 Answers

The TaskScheduler API (which backs some of the Spring Scheduling behavior) seems to be defined to prevent the behavior you are requesting

Schedule the given Runnable, invoking it at the specified execution time and subsequently with the given period.

Parameters

  • period the interval between successive executions of the task (in milliseconds)

subsequently and successive seem to indicate that the next execution will only occur after the current execution is complete.

What's more, ScheduledExecutorService#scheduleAtFixedRate(..) (which the built-in TaskScheduler implementations use) also says

If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute.

So there's another layer of the implementation that prevents the behavior you want.

A possible solution, and one I don't recommend since the API doesn't seem to be built around it, is to define and provide your own TaskScheduler that does run the task concurrently. Look into @EnableScheduling and SchedulingConfigurer for how to register a TaskScheduler.

like image 126
Sotirios Delimanolis Avatar answered Sep 20 '22 13:09

Sotirios Delimanolis


the best solution i have found so far is to simply use a delegate to make the method calls async. this is only preferable because it allows me to declare the schedule in the same class as the method which does the work:

@Service
public class AsyncRunner {
    @Async
    public void run(Runnable runnable) {
        runnable.run();
    }
}

@Service
public class MyTask{
    ...
    @Scheduled(fixedRate = 1000)
    public void scheduleIt(){
        asyncRunner.run(this::doIt);
    }
    public void doIt(){
        // this sometimes takes >1000ms, but the next execution is on time
        ...
    }
}
like image 26
james turner Avatar answered Sep 21 '22 13:09

james turner