javaspringspring-mvcjava-8spring-4

SEVERE: Async timeout for GET [ Streaming huge data Using Spring MVC 4.3.x, Java 8, Tomcat 7 ]


I am using Spring MVC 4.3.9.RELEASE, Java 8, Tomcat 7

My code is as below,

@Controller
@EnableWebMvc
public class StreamRecordsController {

    @RequestMapping(value = "/streamrecords/{elementname}", method = RequestMethod.GET, 
                    produces = "application/json; charset=UTF-8")
    @ResponseBody
    public ResponseEntity<StreamingResponseBody> streamRecords(
            HttpServletRequest request, 
            HttpServletResponse response,
            @PathVariable(value = "elementname", required = true) String elementName,
            @RequestParam(value = "customerid", required = true) long customerId,
            @RequestParam(value = "userid", required = true) long userId) throws Exception {
            StreamingResponseBody responseBody = outputStream -> {
                    /**
                    * 1. FETCH Data from MongoDB using dbcursor and convert to json using pagination.
                    * 2. Write json to outputStream.
                    */
                };
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=sample.json")
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(responseBody);
        }

}

I am getting error as below,

May 10, 2019 11:45:41 AM org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver handleAsyncRequestTimeoutException
SEVERE: Async timeout for GET [/server/streamrecords/xyz]
May 10, 2019 11:46:01 AM org.apache.catalina.connector.CoyoteAdapter checkRecycled
INFO: Encountered a non-recycled response and recycled it forcedly.
org.apache.catalina.connector.CoyoteAdapter$RecycleRequiredException
        at org.apache.catalina.connector.CoyoteAdapter.checkRecycled(CoyoteAdapter.java:634)
        at org.apache.coyote.http11.AbstractHttp11Processor.recycle(AbstractHttp11Processor.java:1909)
        at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.release(Http11AprProtocol.java:245)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:720)
        at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2574)
        at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2563)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)

org.apache.catalina.connector.ClientAbortException: java.io.IOException
        at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:370)
        at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:334)
        at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:101)
        at com.entrib.emg.server.rest.api.services.ElementService.lambda$streamRecords$1(ElementService.java:170)
        at org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler$StreamingResponseBodyTask.call(StreamingResponseBodyReturnValueHandler.java:106)
        at org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler$StreamingResponseBodyTask.call(StreamingResponseBodyReturnValueHandler.java:93)
        at org.springframework.web.context.request.async.WebAsyncManager$4.run(WebAsyncManager.java:316)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.IOException
        at org.apache.coyote.http11.InternalAprOutputBuffer.flushBuffer(InternalAprOutputBuffer.java:205)
        at org.apache.coyote.http11.InternalAprOutputBuffer.flush(InternalAprOutputBuffer.java:109)
        at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:850)
        at org.apache.coyote.Response.action(Response.java:171)
        at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:366)
        ... 9 more

QUESTION:

What am I missing? How can I make my async request not timeout and wait until the outputStream is gracefully shutdown?


Solution

  • The Only thing I had to do was to extend org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; and override its method configureAsyncSupport(AsyncSupportConfigurer configurer). You can set the timeout using below code.

    @Controller
    @EnableWebMvc
    public class StreamRecordsController extends WebMvcConfigurerAdapter{
    
        @Override
        public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
            configurer.setDefaultTimeout(100000000); //in milliseconds (20 hours)
            super.configureAsyncSupport(configurer);
        }
    
        @RequestMapping(value = "/streamrecords/{elementname}", method = RequestMethod.GET, 
                        produces = "application/json; charset=UTF-8")
        @ResponseBody
        public ResponseEntity<StreamingResponseBody> streamRecords(
                HttpServletRequest request, 
                HttpServletResponse response,
                @PathVariable(value = "elementname", required = true) String elementName,
                @RequestParam(value = "customerid", required = true) long customerId,
                @RequestParam(value = "userid", required = true) long userId) throws Exception {
                StreamingResponseBody responseBody = outputStream -> {
                        /**
                        * 1. FETCH Data from MongoDB using dbcursor and convert to json using pagination.
                        * 2. Write json to outputStream.
                        */
                    };
                return ResponseEntity.ok()
                        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=sample.json")
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(responseBody);
            }
    
    }