0

I joined a new team in my company and I see them using a "ThreadPoolTaskExecutor" extensively. It is basically a Backend REST application for a Front-End, that calls other SOAP APIs and returns the result to the client - simply a passthrough. 99% of the times, each REST endpoint is only calling a single SOAP API and returns the response in json format to the client. However, though it's just a single SOAP call, they use the "ThreadPoolTaskExecutor" and call the .get() blocking method.

ThreadPoolTaskExecutor @Bean on @Configuration class:

@Bean(name=“soapAsyncExecutor")
@Qualifier("soapAsyncExecutor")
   public ThreadPoolTaskExecutor getSoapAsyncExecutor() {
    final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    final ThreadFactory customThreadfactory = new ThreadFactoryBuilder()
        .setNameFormat(“SoapExecutor-%d")
        .setUncaughtExceptionHandler(uncaughtExceptionHandler())
        .setDaemon(true)
        .build();
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.setCorePoolSize(80);
    executor.setMaxPoolSize(200);
    executor.setQueueCapacity(80);
    executor.setKeepAliveSeconds(60);
    executor.setAllowCoreThreadTimeOut(true);
    executor.setThreadFactory(customThreadfactory);
    executor.afterPropertiesSet();
    return executor;
   }

Code on @Service:

@Async("soapAsyncExecutor")
@Override
public Future<OrderInfo> getOrderInfoAsync(final String orderId) {
    return new AsyncResult<OrderInfo>(this.getOrderInfo(orderId);
}

private OrderInfo getOrderInfo(final String orderId) {
    // return result from SOAP call which typically take 1 second
}

and then from the @RestController Async method above is called like this:

@GetMapping(value = "/order/{orderId}")
public OrderInfo getOrderInfo(@PathVariable("orderId") final String orderId) {
   OrderInfo orderInfo = orderService.getOrderInfoAsync(orderId).get();
   return orderInfo;
}

As I understand, each request to a REST endpoint in Spring, spins up a new thread anyway (let's call this the main thread for my lack of a better word). And calling the ".get()" method there is blocking the main thread. My question is, during this blocking period,

  1. What is the official state of the main thread - "Blocked" or "Waiting"? If Blocked, is there even a slightest benefit of using a "ThreadPoolTaskExecutor" like this for a single job as it makes the main thread block anyway?
  2. Can this code be tweaked in any way to bring benefit or does it make no sense to use async programming for a single job like this inside a RestController endpoint?
user1102532
  • 495
  • 6
  • 16

1 Answers1

1

What is the official state of the main thread - "Blocked" or "Waiting"?

Blocked.

If Blocked, is there even a slightest benefit of using a "ThreadPoolTaskExecutor" like this for a single job as it makes the main thread block anyway?

Nope. There is none. In fact, I would wager on dispatching the task to a thread pool introducing extra overhead. It would make sense if the endpoint was to wait for multiple tasks to complete, though.

Can this code be tweaked in any way to bring benefit or does it make no sense to use async programming for a single job like this inside a RestController endpoint?

Sure. Here's an example. Here's another one. Essentially, in your scenario, it would be enough to just return the CompletableFuture instead of calling .get().

crizzis
  • 9,978
  • 2
  • 28
  • 47
  • Thanks for your answer @crizziz. So when we do the following, the callback (supplied with thenApply) gets executed in the main thread or in the pool? CompletableFuture.supplyAsync(() -> { // callback function to massage the returned data }, threadPool::execute).thenApply(result -> { // massage result return result; }); – user1102532 Apr 23 '21 at 16:32
  • [This question](https://stackoverflow.com/questions/47489338/what-is-the-difference-between-thenapply-and-thenapplyasync-of-java-completablef) will clear things up a bit – crizzis Apr 23 '21 at 16:44