java - 当客户端关闭连接时,Spring StreamingResponseBody 请求线程未清理

标签 java spring multithreading tomcat

我在 Controller 中有一个端点,它返回一个 StreamingResponseBody,用于向客户端发送文件。代码大致如下所示:

@RestController
@RequestMapping(value="api")
public class Controller {
    static class GetContentResponse implements StreamingResponseBody {

        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            while (!finished) {
                try {
                    byte[] chunk = <get next chunk>;
                    outputStream.write(chunk);
                } catch (InterruptedException e) {
                    throw new RuntimeException("Interrupted!", e);
                }
            }
            outputStream.close();
        }
    }


    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public ResponseEntity<StreamingResponseBody> get(@PathVariable(value = "id") String id)
        throws FaultResponse, InterruptedException {

        GetContentResponse getContentResponse = new GetContentResponse();

        HttpHeaders header = new HttpHeaders();

        return new ResponseEntity<>(getContentResponse, header, HttpStatus.OK);
    }
}

我通过以下方式设置异步请求的超时:

@Configuration
public class AsyncConfiguration extends WebMvcConfigurerAdapter {
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(5000);
    }
}

如果客户端取消请求,响应线程将挂起,不会超时也不会被清理。线程的堆栈跟踪是:

Name: MvcAsync9
State: TIMED_WAITING on java.util.concurrent.CountDownLatch$Sync@4c51a108
Total blocked: 1  Total waited: 31

Stack trace: 
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedNanos(AbstractQueuedSynchronizer.java:1037)
java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireSharedNanos(AbstractQueuedSynchronizer.java:1328)
java.util.concurrent.CountDownLatch.await(CountDownLatch.java:277)
org.apache.tomcat.util.net.NioEndpoint$KeyAttachment.awaitLatch(NioEndpoint.java:1361)
org.apache.tomcat.util.net.NioEndpoint$KeyAttachment.awaitWriteLatch(NioEndpoint.java:1364)
org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:114)
org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:172)
org.apache.coyote.http11.InternalNioOutputBuffer.writeToSocket(InternalNioOutputBuffer.java:139)
   - locked org.apache.coyote.http11.InternalNioOutputBuffer@62f2053a
org.apache.coyote.http11.InternalNioOutputBuffer.addToBB(InternalNioOutputBuffer.java:197)
   - locked org.apache.coyote.http11.InternalNioOutputBuffer@62f2053a
org.apache.coyote.http11.InternalNioOutputBuffer.access$000(InternalNioOutputBuffer.java:41)
org.apache.coyote.http11.InternalNioOutputBuffer$SocketOutputBuffer.doWrite(InternalNioOutputBuffer.java:320)
org.apache.coyote.http11.filters.IdentityOutputFilter.doWrite(IdentityOutputFilter.java:84)
org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:256)
org.apache.coyote.Response.doWrite(Response.java:501)
org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:388)
org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:344)
org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:418)
org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:406)
org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:97)
org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:90)
org.springframework.security.web.context.OnCommittedResponseWrapper$SaveContextServletOutputStream.write(OnCommittedResponseWrapper.java:537)
com.mackie.test.Controller$GetContentResponse.writeTo(Controller.java:98)
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler$StreamingResponseBodyTask.call(StreamingResponseBodyReturnValueHandler.java:108)
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler$StreamingResponseBodyTask.call(StreamingResponseBodyReturnValueHandler.java:94)
org.springframework.web.context.request.async.WebAsyncManager$4.run(WebAsyncManager.java:316)
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
java.util.concurrent.FutureTask.run(FutureTask.java:266)
java.lang.Thread.run(Thread.java:745)

当客户端断开连接时,我如何确保线程被正确销毁?

最佳答案

我自己发现了问题:服务器和客户端之间有一个代理,它没有正确地中继连接的关闭。没有代理一切正常。

关于java - 当客户端关闭连接时,Spring StreamingResponseBody 请求线程未清理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42981008/

相关文章:

java - 为什么 spring Data REST 在序列化实体时忽略 @javax.persistence.Version 字段?

c - Windows 线程 API : Calculate PI value with multiple threads

c# - 当我们创建并启动一个新的 .NET 线程时,它会创建一个新的操作系统级线程吗?

java 图形 - 具有两种颜色的形状

java - 添加 checkstyle 作为预提交 git 钩子(Hook)

java - Android登录应用程序未登录用户

java - 使用 AOP 的 Spring session 范围 bean 中的问题

spring - 找不到 maven 依赖项的 Postgresql 驱动程序

c# - C#线程和属性第二部分

java - 空对象引用上的错误 : Attempt to invoke virtual method . ..