Pit log (asynchronous thread disappears abnormally & RequestContextHolder)

Keywords: Spring jvm

In a spring MVC project, we can usually use the following code to obtain the HttpServletRequest object bound to the current thread:

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

In the recent project development of the company, module A is responsible for statistics of business data and sending emails. Its interface is provided to A scheduler module to call through A scheduled task. On the other side of the scheduler module, the interface of the scheduled module is required to "return immediately". That is to say, when the triggering conditions are met, the scheduler initiates A call to module A, and module A should respond immediately and execute the task asynchronously. After the task is executed, the scheduler is informed that A certain task has been completed through A callback interface.

At first, I didn't pay attention to the need for "immediate return". The interface was processed in A synchronous way. Therefore, when calling the data statistics interface of module A, it was very time-consuming, and the scheduler was late to receive the response from module A, so I thought that this task failed, triggered the failure retry, and constantly called the data statistics interface of module A. Finally, the data statistics interface of module A is called many times.

Later, I changed to asynchronous processing and put the time-consuming operation of data statistics in completabilefuture.runasync. Then there was an error. The email of the data report was not sent out, and there was no error record in the log. Finally, when debugging locally, debug step by step, and find that the program stops at a place where the context Request object is obtained. This is the line below. When the program reaches this code, it stops, and the subsequent code is not executed, and the log does not output error information.

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

I open the debug mode, follow the inside of this method, and find that I can't get the Request object of the current context, and go to the code block that throws the exception

/** RequestContextHolder.class**/
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
        RequestAttributes attributes = getRequestAttributes();
        if (attributes == null) {
            if (jsfPresent) {
                attributes = RequestContextHolder.FacesRequestAttributesFactory.getFacesRequestAttributes();
            }

            if (attributes == null) {
                throw new IllegalStateException("No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
            }
        }

        return attributes;
    }

It's reasonable to say that this runtime exception is thrown out, and the JVM should catch and print out its error information, but this exception is not caught. It's learned from the data search that the exception occurred in the sub thread will not be thrown up to the main thread. But the runtime exception in the main thread is caught and handled by the JVM. You can try the following code:

public static void main(String[] args) throws InterruptedException {
        CompletableFuture.runAsync(() -> {
           System.out.println("start"); 
           String a = null;
           String[] split = a.split(",");
           System.out.println("end");
        });
        System.out.println("wait");
    	//Here, the main thread sleeps for 2s to ensure that the asynchronous thread is executed
        Thread.sleep(2000);
        System.out.println("exit");
    }

The asynchronous code block that should have reported NPE did not see any information in the console after execution.

In addition, note that the Request context obtained by RequestContextHolder.currentRequestAttributes() is bound to the thread. The specific logic is in the processRequest method in the FrameworkServlet. The DispatcherServlet in spring MVC inherits from the FrameworkServlet. When a Request request arrives, trigger the service() method. In the FrameworkServlet, processrequ will be called first EST, in this method, use ThreadLocal to save the current context to the ThreadLocal variable of RequestContextHolder. In the asynchronous code block, the running thread is obviously not the same as the thread processing the Request, so the Request context cannot be obtained.

When run-time exception occurs in asynchronous code block, how to deal with it needs further study.
The initial solution is to wrap a try catch inside the asynchronous code block and handle exceptions in the catch clause.
Or when using CompletableFuture to perform asynchronous tasks, save the execution of the task with the Future variable, and then call the get method to make the exception in the asynchronous block thrown into the main thread.

Published 20 original articles, won praise 2, visited 492
Private letter follow

Posted by Chamza on Tue, 11 Feb 2020 04:40:19 -0800