2

I want to implement multiple @Scheduled(with fixed delay) tasks, each with their own thread pools.

@Scheduled(fixedDelayString = "30000")
public void createOrderSchedule() {
    //create 10 orders concurrently; wait for all to be finished
    createOrder(10);
}

@Scheduled(fixedDelayString = "30000")
public void processOrderSchedule() {
    //process 10 orders concurrently; wait for all to be finished
}

@Scheduled(fixedDelayString = "30000")
public void notifySchedule() {
    //send notification for 10 orders concurrently; wait for all to be finished
}

I managed to create different ThreadPoolTaskExecutor for every scheduler as below:

@Bean("orderPool")
public ThreadPoolTaskExecutor createOrderTaskExecutor() {
    ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
    pool.setCorePoolSize(5);
    pool.setMaxPoolSize(10);
    pool.setThreadNamePrefix("order-thread-pool-");
    pool.setWaitForTasksToCompleteOnShutdown(true);
    return pool;
}

..

I provided @Async over each task.

@Async("orderPool")
public void createOrder(Integer noOforders) {..}

and a task scheduler config

@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(3);
    return threadPoolTaskScheduler;
}

I used CompletableFuture.allOf(..).join(); to wait for each task to complete, but it blocks every other @Scheduled tasks.

To sum up, I want to achieve following:

  1. Each @Scheduled tasks should run independently without blocking other @Scheduled tasks.
  2. Each @Scheduled tasks should have it's own thread pool, so that it can run multiple sub tasks(say 10) concurrently.
  3. Each @Scheduled tasks must wait for each trigger to complete without getting invoked again.

How can I achieve this?

being_ethereal
  • 795
  • 7
  • 26

1 Answers1

1

After being at it for almost 18 hours straight, I was able to achieve what I asked in above question. Apologies for being this late.

So stream APIs provides interfaces like IntStream, etc to stream the elements in parallel. This led me to create n orders parallely. (At the same time, process k orders parallely, in different scheduler. And so on.)

IntStream.range(0, inputIds.size())
        .parallel().forEach(index -> createOrder(inputIds.get(index)));

This was just as this simple. 1 use-case solved. Now I wanted this scheduler to have it's own pool. Found out that IntStream.parallel() uses ForkJoinPool, which is the successor of our own ExecutorService and to my surprise, spring provides a preconfigured factory bean for ForkJoinPool, ie, ForkJoinPoolFactoryBean. So I created a bean named createOrderExecutor.

@Bean("createOrderExecutor")
public ForkJoinPoolFactoryBean createOrderExecutor() {
    ForkJoinPoolFactoryBean createOrderPoolFactoryBean = new ForkJoinPoolFactoryBean();
    createOrderPoolFactoryBean.setParallelism(10);
    createOrderPoolFactoryBean.setAsyncMode(true);
    createOrderPoolFactoryBean.setUncaughtExceptionHandler(null);
    createOrderPoolFactoryBean.setThreadFactory(p -> {
        final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(p);
        worker.setName("create-order-pool-" + worker.getPoolIndex());
        return worker;
    });
    return createOrderPoolFactoryBean;
}

I autowired this bean in my scheduler class, and submitted all orders simultaneously, as shown below.

createOrderExecutor.getObject().submit(() -> IntStream.range(0, inputIds.size())
                .parallel().forEach(index -> createOrder(inputIds.get(index))));

There. 2nd use-case solved. Now this won't wait for all the parallel tasks to finish, it will just trigger them async-ly. Now, ForkJoinTask(that submit() returns) provides a get() method, which waits for the computation to complete & returns the result. (But I don't need result, I would rather surround them with try-catch. And, I'll just wait for the completion.)

@Scheduled(fixedDelayString = "5000")
public void createOrderScheduler() {
        createOrderExecutor.getObject().submit(() -> IntStream.range(0, inputIds.size())
                .parallel().forEach(index -> createOrder(inputIds.get(index)))).get();
}

This solved my last use-case. I did this for all my schedulers in the application.

Believe me, I tried with almost all the implementations of CompletableFuture available online, but wasn't able to achieve all this.

being_ethereal
  • 795
  • 7
  • 26