Spring Boot Async 函数发出 AsyncRequestTimeoutException 然后下一次连接失败

Jim*_* M. 5 spring tomcat spring-mvc spring-boot

问题

我有一个用于发布请求的处理程序,响应可能需要一段时间,所以我流式传输它。如果响应时间太长,我会收到 AsyncRequestTimeoutException 我通过@ControllerAdvice处理程序处理该异常。问题是,一旦我收到该错误,下一个进入的连接在尝试连接时就会收到错误。

卷曲

(52) 服务器的空回复

邮递员

错误:套接字挂起

在服务器中,当连接进入时,我确实看到以下日志消息

2020-10-13 11:53:26.090  INFO 22536 --- [nio-8080-exec-7] o.a.catalina.connector.CoyoteAdapter     : Encountered a non-recycled response and recycled it forcedly.

org.apache.catalina.connector.CoyoteAdapter$RecycleRequiredException: null
    at org.apache.catalina.connector.CoyoteAdapter.checkRecycled(CoyoteAdapter.java:525) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
    at org.apache.coyote.http11.Http11Processor.recycle(Http11Processor.java:1364) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.release(AbstractProtocol.java:1072) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
Run Code Online (Sandbox Code Playgroud)

查看 CoyoteAdapter,这似乎是一条信息性消息,并且没有迹象表明传入连接将不会被处理。

问题

在获取AsyncRequestTimeoutException后,我需要做什么才能让 Spring Boot/Tomcat 正确处理下一个请求(注意:在某些情况下, AsyncRequestTimeoutException是预期的,所以我不关心这一点。我已经设置了spring. mvc.async.request-timeout到我想要的位置)。

系统

我的系统是 Mac OSx,但在 Docker 容器中重复 ( openjdk:11-jre-slim )

Spring Boot:2.3.4.RELEASE(嵌入了 Tomcat 9.0.38)

示例应用程序

仅使用spring-boot-starter-web使用 Spring Iniatlizr 创建

要复制的控制器代码

2020-10-13 11:53:26.090  INFO 22536 --- [nio-8080-exec-7] o.a.catalina.connector.CoyoteAdapter     : Encountered a non-recycled response and recycled it forcedly.

org.apache.catalina.connector.CoyoteAdapter$RecycleRequiredException: null
    at org.apache.catalina.connector.CoyoteAdapter.checkRecycled(CoyoteAdapter.java:525) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
    at org.apache.coyote.http11.Http11Processor.recycle(Http11Processor.java:1364) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.release(AbstractProtocol.java:1072) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
Run Code Online (Sandbox Code Playgroud)

卷曲命令重复问题

@RestController
@RequestMapping("/test")
public class MainController {
    private final static Logger log = LoggerFactory.getLogger(MainController.class);
    private final ObjectMapper jsonMapper;
    private final Random random;

    @Autowired
    public MainController(ObjectMapper jsonMapper) {
        this.jsonMapper = jsonMapper;
        this.random = new Random();
    }

    @PostMapping(value = "streamIt", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<StreamingResponseBody> streamIt(@RequestBody final List<Integer> param) {
        log.info("streamIt starting with {} requests", param.size());
        // Stream the response, this way the request shouldn't time out if there
        // are a lot of elements in the passed in param list.
        StreamingResponseBody stream = out -> {
            LocalDateTime start = LocalDateTime.now();
            try {
                // Start the array out
                out.write('[');
                // flag to indicate if we have to add a comma for array separation
                boolean firstItemWritten = false;
                for (final Integer request : param) {
                    if (!firstItemWritten) {
                        firstItemWritten = true;
                    } else {
                        log.debug("Writing comma!");
                        // Write a comma to the stream
                        out.write(',');
                    }
                    Response response = calculateResponse(request);
                    out.write(jsonMapper.writeValueAsString(response).getBytes());
                    out.flush();
                    log.debug("Wrote value.");
                }
            } catch (IOException io) {
                log.error("Caught io exception");
            } finally {
                out.write(']');
                try {
                    out.close();
                } catch (Exception ioe) {
                    log.warn("Exception closing StreamingResponseBody," +
                            " error=\"{}\"", ioe.toString());
                }
                log.info("streamIt exiting, it took {}.", Duration.between(start, LocalDateTime.now()));
            }
        };
        return new ResponseEntity<>(stream, HttpStatus.OK);
    }

    private Response calculateResponse(Integer request) {
        // Randomly get a "calculation" delay between 15ms and 500ms
        long sleep = Math.max(15L, random.nextInt(500));
        log.debug("Sleeping {}ms for request={}", sleep, request);
        try {
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
            // I do get this error logged, but this would not
            // happen in my actual app since I don't really do a 'sleep'
            log.error("Caught interrupted exception pretending to calculate response...");
        }
        return new Response(request);
    }
}
Run Code Online (Sandbox Code Playgroud)

谢谢