Spring 6.0 升级 6.1 UT010029: Stream is closed

工程:Spring + JBOSS EAP
现象:Spring Framework 6.0.23 升级到 6.1.16后,发生org.springframework.web.context.request.async.AsyncRequestNotUsableException: ServletOutputStream failed to write: UT010029: Stream is closed
Log:

org.springframework.web.context.request.async.AsyncRequestNotUsableException: ServletOutputStream failed to write: UT010029: Stream is closed
    at org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleHttpServletResponse.handleIOException(StandardServletAsyncWebRequest.java:343) ~[spring-web-6.1.16.jar:6.1.16]
    at org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleServletOutputStream.write(StandardServletAsyncWebRequest.java:401) ~[spring-web-6.1.16.jar:6.1.16]
    at java.io.OutputStream.write(OutputStream.java:127) ~[?:?]
    at org.springframework.util.StreamUtils.copy(StreamUtils.java:134) ~[spring-core-6.1.16.jar:6.1.16]
    at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:128) ~[spring-web-6.1.16.jar:6.1.16]
    at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:44) ~[spring-web-6.1.16.jar:6.1.16]
    at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:235) ~[spring-web-6.1.16.jar:6.1.16]
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:300) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:192) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78) ~[spring-web-6.1.16.jar:6.1.16]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:136) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:547) ~[jakarta.servlet-api-6.0.0.redhat-00001.jar!/:6.0.0.redhat-00001]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.16.jar:6.1.16]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614) ~[jakarta.servlet-api-6.0.0.redhat-00001.jar!/:6.0.0.redhat-00001]
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:219) ~[spring-security-web-6.1.2.jar:6.1.2]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) ~[spring-security-web-6.1.2.jar:6.1.2]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) ~[spring-web-6.1.16.jar:6.1.16]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) ~[spring-web-6.1.16.jar:6.1.16]
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.lambda$handleRequest$1(ElytronRunAsHandler.java:68) ~[?:?]
    at org.wildfly.security.auth.server.FlexibleIdentityAssociation.runAsFunctionEx(FlexibleIdentityAssociation.java:103) ~[wildfly-elytron-auth-server-2.2.2.Final-redhat-00001.jar!/:2.2.2.Final-redhat-00001]
    at org.wildfly.security.auth.server.Scoped.runAsFunctionEx(Scoped.java:161) ~[wildfly-elytron-auth-server-2.2.2.Final-redhat-00001.jar!/:2.2.2.Final-redhat-00001]
    at org.wildfly.security.auth.server.Scoped.runAs(Scoped.java:73) ~[wildfly-elytron-auth-server-2.2.2.Final-redhat-00001.jar!/:2.2.2.Final-redhat-00001]
    at org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.handleRequest(ElytronRunAsHandler.java:67) ~[?:?]
    at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) ~[undertow-core-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) ~[undertow-core-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at org.wildfly.elytron.web.undertow.server.servlet.CleanUpHandler.handleRequest(CleanUpHandler.java:38) ~[?:?]
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61) ~[?:?]
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68) ~[?:?]
    at io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:276) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:132) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1430) ~[?:?]
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1430) ~[?:?]
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1430) ~[?:?]
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1430) ~[?:?]
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:256) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:101) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:393) ~[undertow-core-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:859) ~[undertow-core-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) ~[?:?]
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990) ~[?:?]
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486) ~[?:?]
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377) ~[?:?]
    at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282) ~[?:?]
    at java.lang.Thread.run(Thread.java:833) ~[?:?]
Caused by: java.io.IOException: UT010029: Stream is closed
    at io.undertow.servlet.spec.ServletOutputStreamImpl.write(ServletOutputStreamImpl.java:139) ~[undertow-servlet-2.3.10.SP3-redhat-00001.jar!/:2.3.10.SP3-redhat-00001]
    at org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleServletOutputStream.write(StandardServletAsyncWebRequest.java:398) ~[spring-web-6.1.16.jar:6.1.16]
    ... 69 more

测试:
Spring 6.0结果: 100s后正常结束
Spring 6.1结果: 90s后看起来Stream被关闭,100s后报上面异常
测试代码

@ResponseBody
public String xxx(HttpServletRequest request, HttpServletResponse response, @RequestBody String jsonText)
        throws Throwable {
    
        // 100s
        Thread.sleep(100000);
        
        // ...

    return jsonTextRslt;
}

求助:为什么Spring6.0没有任何问题,升级6.1后出现这个问题?
PS:未更改任何配置文件

从 Spring 6.0 升级到 6.1 后,你遇到 AsyncRequestNotUsableException: ServletOutputStream failed to write: UT010029: Stream is closed 的问题,原因可能如下:

可能的原因

  1. Spring 6.1 的异步处理变更

    • 在 Spring 6.1 版本中,异步请求 (async request) 的处理方式可能发生了变化,导致 ServletOutputStream 在你的 Thread.sleep(100000) 执行后已经被关闭,导致写入失败。
  2. Undertow 超时机制

    • 你的 Thread.sleep(100000)(100 秒)可能超过了 Undertow 的异步请求超时时间,导致 Servlet 关闭流,无法写入数据。
    • Undertow 默认的 async-timeout 可能小于 100 秒,可以尝试调整它。
  3. Servlet 容器行为变化

    • Spring 6.1 可能对 StandardServletAsyncWebRequest 进行了修改,使得某些超时或阻塞操作的行为不同,导致流关闭。

解决方案

方案 1:使用 DeferredResultCallable 进行异步处理

Spring 提供了更好的方式来处理长时间运行的请求,如 DeferredResultCallable

@ResponseBody
@RequestMapping("/async")
public DeferredResult<String> asyncProcess(@RequestBody String jsonText) {
    DeferredResult<String> deferredResult = new DeferredResult<>(110000L); // 设置超时 110s

    new Thread(() -> {
        try {
            Thread.sleep(100000); // 模拟长时间任务
            deferredResult.setResult(jsonText);
        } catch (InterruptedException e) {
            deferredResult.setErrorResult("Error processing request");
        }
    }).start();

    return deferredResult;
}

优势

  • Spring 6.1 支持的标准异步处理方式,不会关闭 ServletOutputStream
  • 避免 Thread.sleep 直接阻塞主线程。

方案 2:调整 Undertow 超时时间

如果你必须使用 Thread.sleep,可以调整 Undertow 的 async-timeout

<subsystem xmlns="urn:jboss:domain:undertow:13.0">
    <server name="default-server">
        <http-listener name="default" socket-binding="http" async-timeout="120000"/>
    </server>
</subsystem>

或者application.properties 设置:

server.undertow.no-request-timeout=120000

作用

  • 增加异步请求超时时间,避免 Stream is closed

总结

  • 推荐方案:使用 DeferredResultCallable 进行异步处理,而不是 Thread.sleep
  • 临时解决方案:调整 Undertow 的 async-timeout,但不推荐长时间阻塞主线程。

你可以尝试这些方法,看是否解决问题!