Sometimes a Servlet must wait for some time-consuming operation, such as waiting for an available JDBC connection or a response from a remote Web service, before generating a response message. In this case, asynchronous processing is defined in the Servlet specification. Since waiting blocks in servlets cause the overall processing capacity of the Web container to be low, time-consuming operations can be placed in another thread for processing, which preserves the connected request and response objects. After the processing is complete, the client can be notified of the results of the processing.

As shown in the figure, Tomcat’s client request is processed by the pipe and finally passes through the pipe of the Wrapper container. At this point, it calls the Service method of the Servlet instance for logical processing and responds to the client after processing. The entire process is handled by threads in Tomcat’s Executor thread pool, and the maximum number of threads in the thread pool is limited, so the shorter and faster the process is, the better. However, if the processing logic in the Servlet takes longer, it can lead to a long-term occupation of Tomcat’s processing thread pool, affecting Tomcat’s overall processing capability.

Servlet synchronization

In order to solve the above problem, asynchronous servlets are introduced. Again, the client request comes in, then passes through the pipe and finally enters the pipe of the Wrapper container. After calling the service of the Servlet instance, an asynchronous context is created to encapsulate the time-consuming logical operation and deliver it to the thread pool defined by the user. Instead of waiting for time-consuming operations to complete before relinquishing the thread, Tomcat’s processing threads can immediately return to the Executor thread pool, increasing Tomcat’s overall processing power. Note that the Request and Response objects need to be held to output Response packets to the client after time-consuming operations are completed.

Servlet asynchronous processing

With a simple asynchronous code to see how Tomcat implements Servlet asynchrony:

public class AsyncServlet extends HttpServlet {

    ScheduledThreadPoolExecutor userExecutor = new ScheduledThreadPoolExecutor(5);

    public void doGet(HttpServletRequest req, HttpServletResponse res) {
        AsyncContext aCtx = req.startAsync(req, res);
        userExecutor.execute(new AsyncHandler(aCtx));
    }

}

public class AsyncHandler implements Runnable {

    private AsyncContext ctx;

    public AsyncHandler(AsyncContext ctx) {
        this.ctx = ctx;
    }

    @Override
    public void run() {
        // Time consuming operation
        PrintWriter pw;
        try {
            pw = ctx.getResponse().getWriter();
            pw.print("done!");
            pw.flush();
            pw.close();
        } catch(IOException e) { e.printStackTrace(); } ctx.complete(); }}Copy the code

We create an AsyncServlet that defines a pool of userExecutor threads dedicated to processing the logical, time-consuming operations of all requests for that Servlet. This will not tie up Tomcat’s internal Executor thread pool and affect the processing of other servlets. The idea is a bit like resource isolation, where time-consuming operations are handled by a designated thread pool without affecting other less time-consuming request processing.

The startAsync method creates an asynchronous context AsyncContext object that encapsulates the request and response objects. A task is then created to handle the time-consuming logic, and the AsyncContext object is used to get the response object and respond to the client, printing “Done!” . When I’m done, I tell Tomcat internally I’m done with the complete method, and Tomcat either recycles the request object or the response object or closes the connection.

Welcome to: