How to prevent overlapping schedules in Spring?
Asked Answered
B

2

50
@Scheduled(fixedDelay = 5000)
public void myJob() {
     Thread.sleep(12000);
}

How can I prevent this spring job from running if the previous routine is not yet finished?

Birthroot answered 4/6, 2014 at 9:4 Comment(1)
my question is counterpart :) How did you managed to run it in parallel? I am trying to do this but it is not working. Even if "fixedRate" is significantly lower than thread.sleep, it is waiting until previous run is finished, first then it is launched again.Indiraindirect
D
55

With fixedDelay, the period is measured after the completion of job, so no worries.

Dasteel answered 4/6, 2014 at 9:12 Comment(2)
That's an interesting statement! What if I later want to switch to cron (which is definitly intended)?Birthroot
Use a single thread executor, that is the default if you don't set a pool size or don't declare a scheduler at all.Dasteel
D
102

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.

furthermore, even if you replace the default Executor with a thread pool based executor, those Executors will typically delay the execution of a task instance until the previously scheduled instance completes. this is true for fixedDelay, fixedInterval, and cron based schedules. for example, this spring configuration will create a ScheduledThreadPoolExecutor that uses a threadpool, but does not allow concurrent instances of the same schedule just as you desire:

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

here is the javadoc for ScheduledThreadPoolExecutor::scheduleAtFixedRate which specifies:

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

note: this functionality does not hold true for @Async tasks. spring will create as many concurrent instances of those as needed (if there are sufficient threads in the pool).

Demur answered 19/5, 2015 at 13:42 Comment(10)
Can this introduce subtle errors or leaks in the future? If a job routinely takes longer to complete (but isn't known to do this) and Spring has a backlog of the same job, can this be risky?Sherwin
@EricMajerus i doubt it. spring almost certainly doesn't create separate objects or pointers for every execution. odds are it just keeps a single pointer to the "next" start time. so there shouldn't be any resource leak even if you have a million jobs "piled up".Demur
this is true for fixedDelay, fixedInterval, and cron based schedules!! Please bold this line, it's real life Saviour!!Thomas
"Executors will typically delay the execution of a task instance until the previously scheduled instance completes" How can I explicitly work around this behaviour? I want my job to run twice concurrently if it takes to long.Impish
@Graslandpinguin i would probably have the scheduled task call the worker task asynchronously. e.g. @Scheduled foo(){Bar.baz();}} class Bar{ @Async baz(){/*work done here*/}} keeping in mind that the async threadpool must be large enough for all your overlapping workers, and if you're workers never complete you will eventually exhaust even the largest thread pool.Demur
What if the method is annotated with multiple @Scheduled annotations like: @Scheduled(cron = "...") @Scheduled(initialDelayString = "PT10S", fixedDelayString = "PT1H")?Intertexture
@Lambda good question! i suggest you test it and find out. just make a method that prints a timestamp and then Thread.sleep(60000). then add 2 @Scheduled annotations to it (one that runs on the first second of every minute and one that runs on the 10th second). let us know if you see 1 timestamp per minute or 2 :)Demur
@james If it isn't documented there's no guarantee that the behavior remains the same between different versions. :)Intertexture
@Lambda even if the behavior is documented, there is still no guarantee the it will remain the same in a future release of spring. that's particularly true for the default values like the single-threaded executor. honestly though, i would expect any serious codebase to specify a multi-threaded task executor like the ScheduledThreadPoolExecutor whose javadoc i've linked above.Demur
This solution works for me, but isn't it possible to achieve the same via application.properties ? I tried with spring.task.execution.pool.size=10 and spring.task.scheduling.pool.size=10 but it made no difference.Scumble
D
55

With fixedDelay, the period is measured after the completion of job, so no worries.

Dasteel answered 4/6, 2014 at 9:12 Comment(2)
That's an interesting statement! What if I later want to switch to cron (which is definitly intended)?Birthroot
Use a single thread executor, that is the default if you don't set a pool size or don't declare a scheduler at all.Dasteel

© 2022 - 2024 — McMap. All rights reserved.