Spring Boot Asynchronous Controller via Callable not working

24 Views Asked by At

I want to make an endpoint that is asynchronous with Spring Boot.

I want to avoid Futures and thus @Async because it forces the use of futures.

I read and applied the code from this article : https://howtodoinjava.com/spring-boot/async-rest-controller-callable/

Here's what I have :

Config class to enable Async and configure threadPool :

package com.howtodoinjava.springasyncexample.config;

@Configuration
@EnableAsync
public class AsyncConfig implements WebMvcConfigurer {

  @Override
  public void configureAsyncSupport(AsyncSupportConfigurer configurer) {

    configurer.setTaskExecutor(mvcTaskExecutor());
    configurer.setDefaultTimeout(30_000);
  }

  @Bean
  public ThreadPoolTaskExecutor mvcTaskExecutor() {

    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setCorePoolSize(10);
    threadPoolTaskExecutor.setThreadNamePrefix("mvc-task-");
    return threadPoolTaskExecutor;
  }
}

Controller code :

package com.howtodoinjava.springasyncexample.web.controller;

import java.util.concurrent.Callable;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
public class HelloWorldCallableController {

    @GetMapping(value = "/testCallable")
    public Callable<String> echoHelloWorld() {
        log.info("controller hit");
        return () -> {
            Thread.sleep(5000);
            log.info("finished long job");
            return "Hello World !!";
        };
    }
}

Application code :

package com.howtodoinjava.springasyncexample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringAsyncExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAsyncExampleApplication.class, args);
    }
}

I open a browser, on two different tabs I make two quick requests, at one second of interval. What I expect is these logs :

controller hit t
controller hit t+1s
finished long job t+5s (first job finishing on work thread 1)
finished long job t+6s (second job finishing on work thread 2)

What I get is this :

2024-03-26 09:50:07.356  INFO 8752 --- [nio-8080-exec-1] c.h.s.w.c.HelloWorldCallableController   : controller hit
2024-03-26 09:50:12.372  INFO 8752 --- [     mvc-task-1] c.h.s.w.c.HelloWorldCallableController   : finished long job
2024-03-26 09:50:12.398  INFO 8752 --- [nio-8080-exec-2] c.h.s.w.c.HelloWorldCallableController   : controller hit
2024-03-26 09:50:17.412  INFO 8752 --- [     mvc-task-2] c.h.s.w.c.HelloWorldCallableController   : finished long job

Clearly we can see that the "controller listening thread" waits for the first listening thread to finish it's job before handling new requests. So this is not asynchronous behavior at all, it's just normal synchronous Controller behavior.

1

There are 1 best solutions below

0
Genku On

In case someone gets got like me to waste time on this, the reason it was happening was the way I was testing it, I was using the browser named "Google Chrome".

I was making two tabs and calling the same URL to test (http://localhost:8080/endpoint) on both tabs, but Chrome seems to use just one thread to make its HTTP requests... So it was waiting for the first request on tab 1 to return before performing the request on tab 2.

I had to test with Postman creating two instances of the request and running them in rapid succession to effectively test two rapid calls. And the code in the original post works.