3

I have a question about thread management for the tomcat server and application. Here is a sample code:

@RequestMapping(path = "/asyncCompletable", method = RequestMethod.GET)
    public CompletableFuture<String> getValueAsyncUsingCompletableFuture() {

        logger.info("Request received");
        CompletableFuture<String> completableFuture
                = CompletableFuture.supplyAsync(this::processRequest);
        logger.info("Servlet thread released");
        return completableFuture;

    }

    private String processRequest() {
        Long delayTime = 10000L;
        logger.info("Start processing request");
        try {
            Thread.sleep(delayTime);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        logger.info("Completed processing request");
        return "Processing done after " + delayTime;
    }

Here, This is a simple API using spring boot. What I have done here?

  • It is simple endpoints that is a simple GET method and returning a string as a response.
  • In method getValueAsyncUsingCompletableFuture I am calling processRequest on CompletableFuture.supplyAsync(this::processRequest). So obviously it will run under separate thread.
  • In method processRequest where only have a Thread.sleep for 10s.
  • Both methods have an entry and exit log.
  • Also the API getValueAsyncUsingCompletableFuture is returning a completableFuture

Now after calling the Endpoints from the browser its output is as expected. It waits 10 seconds and provides a response. The output log is given below. enter image description here

Here, 

  • The first and second line of log is showing under nio-8080-exec-8 thread. It also showing request received and servlet thread released.
  • In the last 2 lines of log for method processRequest. Here it is showing for CompletableFuture execution part. It is handling under onPool-worker-2 thread.

Now, Here is my question:

  1. During the execution of processRequest method, is it released the tomcat allocated thread nio-8080-exec-8? I am telling based on the log. Is this thread released and go back to tomcat connection pool?
  2. If the tomcat thread released then how the client is getting a response after the execution of completableFuture is done?
  3. Can you please describe how thread allocation occur when request come to tomcat server, and spring application.

Thanks in advance

user207421
  • 305,947
  • 44
  • 307
  • 483
Md. Sajedul Karim
  • 6,749
  • 3
  • 61
  • 87
  • 1
    Why don't you use the reactive stack? https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html – Simon Martinelli May 26 '21 at 15:57
  • Sir, Thank you for your comments. As far as I know, both WebFlux and CompletableFuture are doing actually the same things. For one of my R&D, I need an answer to these questions. – Md. Sajedul Karim May 26 '21 at 15:59
  • They are not doing the same thing because WebFlux is Non-blocking but MVC is blocking that means the Thread is blocked even if you are returning a completable future – Simon Martinelli May 26 '21 at 16:06
  • 1
    1. yes, 2. that is how the async part of the servlet-api works. 3. read the servlet spec which explains that. – M. Deinum May 26 '21 at 17:10
  • "So obviously it will run under separate thread" you have to be very careful with your assumptions. While `supplyAsync` will indeed run in a different thread, the dependent actions [might not](https://stackoverflow.com/questions/65685715/will-a-chain-of-method-calls-completablefuture-api-execute-asynchronously-if-t/65691463#65691463) – Eugene May 27 '21 at 01:11

1 Answers1

2

Spring uses the ServletRequest#startAsync() method introduced in Servlet 3.0 (see Oracle's tutorial). This means that:

  1. The Tomcat thread http-nio-8080-exec-8 is returned to its executor shortly after getValueAsyncUsingCompletableFuture is called, but the response is not yet committed to the client as in the synchronous case. Spring switches the ServletRequest into asynchronous mode whenever your handler returns a CompletionStage.
  2. After your method getValueAsyncUsingCompletableFuture exits, the ServletRequest and ServletResponse can still be used to send data to the client. Spring appends a stage to your CompletableFuture, which writes the result to ServletResponse and ends the request using AsyncContext#complete(). This stage can be executed on any thread. In your case it executes on the ForkJoinPool since you launched the CompletableFuture using the default executor. However any executor would be fine.
  3. Tomcat allocates a thread to execute getValueAsyncUsingCompletableFuture (or more precisely DispatcherServlet#service). The rest of the allocations is up to you.
Piotr P. Karwasz
  • 12,857
  • 3
  • 20
  • 43
  • 2
    if Spring indeed attaches a stage, that stage can be executed outside of the `ForkJoinPool`, see [this](https://stackoverflow.com/questions/65685715/will-a-chain-of-method-calls-completablefuture-api-execute-asynchronously-if-t/65691463#65691463). But if they use a dedicated executor, than you are correct. I am very interested in the code where they do this – Eugene May 27 '21 at 01:14
  • The stage is executed on the same thread as the previous stage (see [DeferredResultMethodResultValueHandler.java](https://github.com/spring-projects/spring-framework/blob/4203e90655a3836fb564f32b6f5c978289e87d60/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java#L93)). However you can change the `Executor` of the previous stage using a `CompletableFuture.supplyAsync` call with two arguments. – Piotr P. Karwasz May 27 '21 at 03:25
  • 1
    it calls `handle` there, and if by the time it is chained to the previous one, `CompletableFuture` is already completed, it will be called on the calling thread, not on the thread that does ``supplyAsync` – Eugene May 27 '21 at 03:35