2

I have a thread-pool initialized as a @Bean for purpose of dedicated execution of a particular @Async method

class MyConfig {
    @Bean(name="myPool")
    public static TaskExecutor getExecutor(){
        ThreadPooltaskExecutor exec = new ThreadPoolTaskExecutor();
        exec.setCorePoolSize(1);
        exec.setMaxPoolSize(1);
        exec.setThreadNamePrefix("my-thread");
        exec.initialize();
        return exec;
    }
}

Above thread-pool as a bean is used in Async method call like below:

public class MyHandler {
    ...
    @Async("myPool")
    public void handle(){
        ...
        logger.INFO("do my thing");
        ...
    }
    ...
}

There are other classes also which are using default Spring @Async pool, like:

public class OtherScheduledTask {
    ...
    @Async
    public void doThat() {
        logger.INFO("doing that thing");
    }
    ...
}

On running application I can see the following in logs:

[pool-1-thread-1] [] c.c.a.m.p.e.c.OtherScheduledTask - do my thing
[myPool] [] c.c.a.m.p.e.c.MyHandler - doing that thing
....

I see above log snippets randomly getting printed and async methods are getting executed by default thread-pool and custom thread pool intermittently

I do have scenarios where I am calling @Async methods from non-async methods

However, I have used my custom thread-pool strictly only with one @Async method handle() in MyHandler and no-where else

Why spring is mixing thread-pools (both custom and default) for executing @async methods?

What is the use of passing bean name in @Async method annotation?

How I can have dedicated thread-pool with one particular async method, so that when it is not executing, let the thread-pool be idle and not getting used for other async methods which should be executed only by default spring thread pool

Details about my environment: Spring boot version 1.4.2 RELEASE JDK 1.8.0

Ashish Mishra
  • 303
  • 4
  • 10
  • Are you sure those are the logs you see. 'Cause they say that the appropriate class was running in the right pools; but then print the wrong statement from each... – BeUndead Jun 22 '18 at 08:46
  • Yes I have printed exact logs. I am clueless how spring-boot is mixing thread-pools for async calls irrespective of the fact that you can define bean name in @Async("bean-name") annotation – Ashish Mishra Jun 22 '18 at 09:54
  • I can see it uses correct pools also, but intermittently mixes them as well. I am not denying the fact that logs do print like this as well: [myPool] [] c.c.a.m.p.e.c.MyHandler - do my thing [pool-1-thread-1] [] c.c.a.m.p.e.c.OtherScheduledTask - doing that thing – Ashish Mishra Jun 22 '18 at 10:10
  • How are you invoking both of those async methods? FYI, if you are calling them from the same class, then you will not see the threadpool assigned to the method in use. See https://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring for more info – Dovmo Jun 22 '18 at 12:34
  • @Dovmo I am invoking one async method with custom pool from non-async method in same class. Other async methods are getting invoked from their respective classes and there is no calls across these methods from other classes – Ashish Mishra Jun 22 '18 at 13:03

2 Answers2

3

One key thing about Spring and proxies is that many annotated features use proxies to provide the functionality. That is @Transactional, @Cacheable and @Async all rely on Spring detecting those annotations and wrapping those beans in a proxy bean.

That being the case, a proxied method can only be used when invoked on the class and not from within the class. See this about the topic.

To quote from a comment:

@Dovmo I am invoking one async method with custom pool from non-async method in same class. Other async methods are getting invoked from their respective classes and there is no calls across these methods from other classes

Try refactoring and calling those @Async methods from another class in your context, or by self-autowiring the class into itself and calling the async methods that way.

Dovmo
  • 8,121
  • 3
  • 30
  • 44
2

[Besides what said about how Spring works with proxy methods - and thus the correct context to invoke @Async methods from:]

Quoting from the official Spring documentation:

By default, Spring will be searching for an associated thread pool definition: either a unique TaskExecutor bean in the context, or an Executor bean named "taskExecutor" otherwise. If neither of the two is resolvable, a SimpleAsyncTaskExecutor will be used to process async method invocations.

Assuming that some annotations are missing from your code snippets, the code only adds another possible executor for Spring to find and use. You need to explicitly override Spring's default SimpleAsyncTaskExecutor implementation:

Method 1 : override the default implementation (From the official documentation)

 @Configuration
 @EnableAsync
 public class AppConfig implements AsyncConfigurer {

     @Override
     public Executor getAsyncExecutor() {
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
         executor.setCorePoolSize(7);
         executor.setMaxPoolSize(42);
         executor.setQueueCapacity(11);
         executor.setThreadNamePrefix("MyExecutor-");
         executor.initialize();
         return executor;
     }

     @Override
     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
         return MyAsyncUncaughtExceptionHandler();
     }
 }

Method 2: Supply a custom and a default implementation:

@Configuration
 @EnableAsync
 public class AppConfig implements AsyncConfigurer {

     // Provide an Executor implementation in lieu of the default Spring implementation
    @Override
    @Bean
    @Primary
    public Executor getAsyncExecutor() {        
        SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("pool1-");
        return executor;
    }

     @Bean(name="myCustomExecutor")
     public Executor getAsyncExecutor() {
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
         executor.setCorePoolSize(7);
         executor.setMaxPoolSize(42);
         executor.setQueueCapacity(11);
         executor.setThreadNamePrefix("MyExecutor-");
         executor.initialize();
         return executor;
     }

     @Override
     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
         return MyAsyncUncaughtExceptionHandler();
     }
 }

P.S: If your getExecutor() is annotated with @Bean:

it is no longer necessary to manually call the executor.initialize() method as this will be invoked automatically when the bean is initialized.

Shlomi Uziel
  • 868
  • 7
  • 15