Humberd Humberd - 18 days ago 12
Java Question

Spring Controller waits for async response instad of managing the next request

I have this request endpoint:

@PostMapping("/verify")
public Callable<String> verify() {
logger.debug("----STARTED REQUEST-----");
Callable<String> callable = () -> {
Thread.sleep(5000);
logger.debug("_____FINISHED REQUEST_____");
return "foo";
};
logger.debug("FREED CONTROLLER");
return callable;
}


It returns a
Callable
object, that sleeps for 5 seconds.

My Goal: When I have multiple incomming requests I want them to be handled asynchronously.

What I mean is that when the first request comes and I return a callable object, then the controller, in the meantime of waiting, should handle the next request. But instead it waits for the callable to complete and only then handles the next request. Here is the log.

[nio-8080-exec-1] c.t.c.lol.task.TaskLolController : ----STARTED REQUEST-----
[nio-8080-exec-1] c.t.c.lol.task.TaskLolController : FREED CONTROLLER
[ MvcAsync1] c.t.c.lol.task.TaskLolController : _____FINISHED REQUEST_____
[nio-8080-exec-3] c.t.c.lol.task.TaskLolController : ----STARTED REQUEST-----
[nio-8080-exec-3] c.t.c.lol.task.TaskLolController : FREED CONTROLLER
[ MvcAsync2] c.t.c.lol.task.TaskLolController : _____FINISHED REQUEST_____
[nio-8080-exec-5] c.t.c.lol.task.TaskLolController : ----STARTED REQUEST-----
[nio-8080-exec-5] c.t.c.lol.task.TaskLolController : FREED CONTROLLER
[ MvcAsync3] c.t.c.lol.task.TaskLolController : _____FINISHED REQUEST_____


What I want:

----STARTED REQUEST-----
FREED CONTROLLER
----STARTED REQUEST-----
FREED CONTROLLER
----STARTED REQUEST-----
FREED CONTROLLER
_____FINISHED REQUEST_____
_____FINISHED REQUEST_____
_____FINISHED REQUEST_____


How can I achieve that?

Answer

Create a service from your callable method

@Service
private class TaskService {
    @Async
    public String execute() {
        try {
            Thread.sleep(5000);
            LOGGER.debug("_____FINISHED REQUEST_____");
            return "foo";
        } catch (InterruptedException e) {
            throw new RuntimeException();
        }
    }

}

In your controller, define the callable to be the execute method of your `TaskService``

@Autowired
TaskService taskService;

@PostMapping("/verify")
public Callable<String> verify() {
    Callable<String> callable = taskservice::execute;

    logger.debug("FREED CONTROLLER");
    return callable;
}

@edit To make @Async annotation work I need to enable async:

@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {
    @Override
    public Executor getAsyncExecutor() {
        return Executors.newWorkStealingPool(); //will use as many threads as your processor has
    }
}