@RestController with StreamingResponseBody async.request-timeout not working

Viewed 3334

I'm trying to get a StreamingResponseBody from a @RestController on a long running job. It times out after 30 seconds, no matter what configuration I try.

This is in Spring Boot 2.0.3. I've used the below test which shows the same behavior to try and get the configuration correct.

@RestController
public class TestController {

    @RequestMapping("/streamtest")
    public StreamingResponseBody handleRequest () {
        return new StreamingResponseBody() {
            @Override
            public void writeTo (OutputStream out) throws IOException {
                for (int i = 0; i < 100000; i++) {
                    out.write((Integer.toString(i) + " - ").getBytes());
                    out.flush();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
       };
   }
}

I've tried the async.request-timeout setting defined here; Async timeout downloading a large file using StreamingResponseBody on Spring Boot

I've tried overriding WebMvcConfig to set the timeout. This method is never called.

@EnableWebMvc
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(3600000);
        WebMvcConfigurer.super.configureAsyncSupport(configurer);
    }
}

I've tried defining the ThreadPoolTaskExecutor:

@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        return new ThreadPoolTaskExecutor();
    }
}

It always times out after 30 seconds with a log;

12:14:28.028 [http-nio-8080-exec-2] DEBUG c.b.b.bof_static.config.BofStaticExceptionHandler - Async timeout for GET [/streamtest]
12:14:28.028 [http-nio-8080-exec-2] DEBUG org.springframework.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
12:14:28.028 [http-nio-8080-exec-2] DEBUG org.springframework.web.servlet.DispatcherServlet - Successfully completed request
12:14:28.030 [http-nio-8080-exec-2] DEBUG o.s.security.web.access.ExceptionTranslationFilter - Chain processed normally

I'm unable to find any other solutions. Can anyone point out what's missing?

3 Answers

Setting the following option in application.properties can solve this:

spring.mvc.async.request-timeout=-1

However, if you implement WebMvcConfigurer anywhere in your code, then the above option will be ignored, so you have to set it up as the following:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    // other config...
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(-1);
    }
}

You can also set it to a positive integer (milliseconds). The value of -1 will completely remove the timeout.

Open the application.properties add the following line

# in milliseconds 
spring.mvc.async.request-timeout=5000 

If this is production code please do not set its value to -1. This in case of a large number of requests can lead to resource starvation. Cause clients will never timeout and will hold on to the resources they are using (thread).

I already had a Spring Boot project set up on my machine with a configuration class like below ,

@Configuration
@EnableWebMvc
@EnableTransactionManagement
public class MyConfiguration extends WebMvcConfigurerAdapter {
  ...
  ...
}

I simply added @EnableAsync to it and below overridden method ,

    @Configuration
    @EnableWebMvc
    @EnableTransactionManagement
    @EnableAsync
    public class MyConfiguration extends WebMvcConfigurerAdapter {
      ...
      ...
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
      configurer.setDefaultTimeout(3600000);
    }
   }

I simply took your method to one of my controller classes and hit it from swagger. I don't see issue for few minutes. When I put your method without above changes to configuration , I do get ,

2018-10-24 11:35:55.085 ERROR 78852 --- [nio-8080-exec-6] c.h.a.a.aop.MyExceptionHandler       : Async timeout for GET [/test/common/streamtest]

so all seems to be working fine for me.

My only guess from your code is that you might have put @EnableAsync at wrong place.

Also, keep things minimal and don't experiment with ThreadPoolTaskExecutor() now and let your request be executed with default - SimpleAsyncTaskExecutor. Once you get your Async working with custom timeout, you can plug it in later.

I am running app in embedded tomcat.

Related