代码之家  ›  专栏  ›  技术社区  ›  palacsint

了解Jetty的“关闭时挂起/未读”警告

  •  2
  • palacsint  · 技术社区  · 8 年前

    java.io.IOException: Closed while Pending/Unready
    

    在启用调试日志之后,我得到了以下stacktrace:

    WARN [jetty-25948] (HttpOutput.java:278)   - java.io.IOException: Closed while Pending/Unready
    DEBUG [jetty-25948] (HttpOutput.java:279)   - 
    java.io.IOException: Closed while Pending/Unready
            at org.eclipse.jetty.server.HttpOutput.close(HttpOutput.java:277) ~[jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.server.Response.closeOutput(Response.java:1044) [jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:488) [jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.server.HttpChannel.run(HttpChannel.java:293) [jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:708) [jetty-util.jar:9.4.8.v20171121]
            at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:626) [jetty-util.jar:9.4.8.v20171121]
            at java.lang.Thread.run(Thread.java:748) [na:1.8.0_151]
    

    这没有太大帮助。

    只有在Jetty调用 onTimeout() 我们的方法 AsyncListener . QA有时可以通过使用 kill -9 在客户端应用程序上。

    杀戮-9 .)

    码头版本:9.4.8.v20171121

    更新(2018-06-27):

    对于@Joakim Erdfelt的有益回答,我没有发现任何 close() 调用我们的代码,但发现可疑的缺少同步。下面是异步轮询servlet的基础:

    public class QueuePollServlet extends HttpServlet {
    
        public QueuePollServlet() {
        }
    
        @Override
        protected void doPost(final HttpServletRequest req, final HttpServletResponse resp)
                throws ServletException, IOException {
            resp.setContentType(MediaType.OCTET_STREAM.type());
            resp.setStatus(HttpServletResponse.SC_OK);
            resp.flushBuffer();
            final AsyncContext async = req.startAsync();
            async.setTimeout(30_000);
            final ServletOutputStream output = resp.getOutputStream();
            final QueueWriteListener writeListener = new QueueWriteListener(async, output);
            async.addListener(writeListener);
            output.setWriteListener(writeListener);
        }
    
        private static class QueueWriteListener implements AsyncListener, WriteListener {
    
            private final AsyncContext asyncContext;
            private final ServletOutputStream output;
    
            public QueueWriteListener(final AsyncContext asyncContext, final ServletOutputStream output) {
                this.asyncContext = checkNotNull(asyncContext, "asyncContext cannot be null");
                this.output = checkNotNull(output, "output cannot be null");
            }
    
            @Override
            public void onWritePossible() throws IOException {
                writeImpl();
            }
    
            private synchronized void writeImpl() throws IOException {
                while (output.isReady()) {
                    final byte[] message = getNextMessage();
                    if (message == null) {
                        output.flush();
                        return;
                    }
                    output.write(message);
                }
            }
    
            private void completeImpl() {
                asyncContext.complete();
            }
    
            public void dataArrived() {
                try {
                    writeImpl();
                } catch (IOException e) {
                    ...
                }
            }
    
            public void noMoreBuffers() {
                completeImpl();
            }
    
            @Override
            public void onTimeout(final AsyncEvent event) throws IOException {
                completeImpl();
            }
    
            @Override
            public void onError(final Throwable t) {
                logger.error("Writer.onError", t);
                completeImpl();
            }
    
    
            ...
        }
    }
    

    可能的种族状况:

    1. DataFeederThread:调用 dataArrived() writeImpl() ,然后它得到了 output.isReady() true .
    2. 超时()
    3. DataFeederThread:调用 output.write() 在while循环中,但找到了已完成的上下文。

    Closed while Pending/Unready 警告还是另一个问题? 我说得对 completeImpl() synchronized

    更新(2018-06-28):

    我们也有类似的 onError 实施 QueueWriteListener 作为以下代码段:

    @Override
    public void onError(final Throwable t) {
        logger.error("Writer.onError", t);
        completeImpl();
    }
    

    无论如何,没有 误差 错误日志围绕 日志消息(查看每个日志消息的两个小时的时间范围),仅EOF,如下所示 DataFeederThread

    DEBUG [DataFeederThread] (HttpOutput.java:224) - 
    org.eclipse.jetty.io.EofException: null
            at org.eclipse.jetty.server.HttpConnection$SendCallback.reset(HttpConnection.java:704) ~[jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.server.HttpConnection$SendCallback.access$300(HttpConnection.java:668) ~[jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.server.HttpConnection.send(HttpConnection.java:526) ~[jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.server.HttpChannel.sendResponse(HttpChannel.java:778) ~[jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.server.HttpChannel.write(HttpChannel.java:834) ~[jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:234) [jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:218) [jetty-server.jar:9.4.8.v20171121]
            at org.eclipse.jetty.server.HttpOutput.flush(HttpOutput.java:392) [jetty-server.jar:9.4.8.v20171121]
            at com.example.QueuePollServlet$QueueWriteListener.writeImpl()
            at com.example.QueuePollServlet$QueueWriteListener.dataArrived()
    
    
    DEBUG [DataFeederThread] (QueuePollServlet.java:217) - messageArrived exception
    org.eclipse.jetty.io.EofException: Closed
            at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:476) ~[jetty-server.jar:9.4.8.v20171121]
            at com.example.QueuePollServlet$QueueWriteListener.writeImpl()
            at com.example.QueuePollServlet$QueueWriteListener.dataArrived()
    
    2 回复  |  直到 8 年前
        1
  •  3
  •   palacsint    8 年前

    有可能复制 Closed while Pending/Unready 使用一些手动调试和简单的 curl

    1. The first breakpoint is in the HttpOutput.write() method right after it changes its state from READY to PENDING :

      case READY:
          if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
              continue;
          // put a breakpoint here
      
    2. The second one in Response.closeOutput()

      case STREAM:
          // put a breakpiont here
          if (!_out.isClosed())
              getOutputStream().close();
          break;
      

    复制步骤:

    1. QueueWriteListener.onWritePossible() 它向输出写入几个字节,然后返回(因为它的输入缓冲区是空的)。
    2. 等待onTimeout事件。
    3. [喷气式飞机2] HttpChannelState.onTimeout() 电话 QueueWriteListener.onTimeout() 打电话来 asyncContext.complete() .
    4. [喷气式飞机2] 在异步超时后计划调度。
    5. [JettyThread2]在第二个断点处暂停
    6. [干膜厚度] DataFeederThread 电话 writeImpl()
    7. 数据馈送线程 电话 (这是输出流)
    8. [干膜厚度] HttpOutput.write() 准备好了 悬而未决的
    9. [干膜厚度] 由于上面的断点,此处暂停
    10. 挂起/未读时关闭 警告。

    所以,实际上是Jetty关闭了这个堆栈(Jetty 9.4.8.v20171121)上的输出流:

    Thread [jetty-19] (Suspended)   
        Response.closeOutput() line: 1043   
        HttpChannelOverHttp(HttpChannel).handle() line: 488 
        HttpChannelOverHttp(HttpChannel).run() line: 293    
        QueuedThreadPool.runJob(Runnable) line: 708 
        QueuedThreadPool$2.run() line: 626  
        Thread.run() line: 748  
    

    制作 onTimeout() synchronized (以及 写入impl() 也是 )在侦听器中没有帮助,因为计划的关闭仍然可以与 writeImpl 数据馈送线程 ). 考虑这个案例:

    1. [JettyThread1]调用 它向输出写入几个字节,然后返回(因为它的输入缓冲区是空的)。
    2. 等待onTimeout事件。
    3. [喷气式飞机2] 电话 QueueWriteListener.onTimeout() 打电话来 asyncContext.complete()
    4. [干膜厚度] 电话 写入impl() onTimeout 尚未完成)
    5. [喷气式飞机2] QueueWriteListener.onTimeout()
    6. [干膜厚度] 能跑
    7. [喷气式飞机2] 在异步超时后计划调度。
    8. [JettyThread2]在第二个断点处暂停
    9. 数据馈送线程 电话 HttpOutput.write() (这是输出流)
    10. [干膜厚度] 改变它的状态 准备好了 悬而未决的
    11. [干膜厚度] 由于上面的断点,此处暂停
    12. 挂起/未读时关闭

    不幸的是,在 asnyContext.complete() 检查是不够的 output.isReady() true 因为码头重新开放 HttpOutput (请参见下面的堆栈),因此您需要在侦听器中为其设置单独的标志。

    Thread [jetty-13] (Suspended (access of field _state in HttpOutput))    
        HttpOutput.reopen() line: 195   
        HttpOutput.recycle() line: 923  
        Response.recycle() line: 138    
        HttpChannelOverHttp(HttpChannel).recycle() line: 269    
        HttpChannelOverHttp.recycle() line: 83  
        HttpConnection.onCompleted() line: 424  
        HttpChannelOverHttp(HttpChannel).onCompleted() line: 695    
        HttpChannelOverHttp(HttpChannel).handle() line: 493 
        HttpChannelOverHttp(HttpChannel).run() line: 293    
        QueuedThreadPool.runJob(Runnable) line: 708 
        QueuedThreadPool$2.run() line: 626  
        Thread.run() line: 748  
    

    此外, isReady() 同时返回 真的 isReady() returns true in closed state - why?

    最终的实现是类似的:

    @Override
    protected void doPost(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType(MediaType.OCTET_STREAM.type());
        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setBufferSize(4096);
        resp.flushBuffer();
        final AsyncContext async = req.startAsync();
        async.setTimeout(5_000); // millis
        final ServletOutputStream output = resp.getOutputStream();
        final QueueWriteListener writeListener = new QueueWriteListener(async, output);
        async.addListener(writeListener);
        output.setWriteListener(writeListener);
    }
    
    private static class QueueWriteListener implements AsyncListener, WriteListener {
    
        private static final Logger logger = LoggerFactory.getLogger(QueueWriteListener.class);
    
        private final AsyncContext asyncContext;
        private final ServletOutputStream output;
    
        @GuardedBy("this")
        private boolean completed = false;
    
        public QueueWriteListener(final AsyncContext asyncContext, final ServletOutputStream output) {
            this.asyncContext = checkNotNull(asyncContext, "asyncContext cannot be null");
            this.output = checkNotNull(output, "output cannot be null");
        }
    
        @Override
        public void onWritePossible() throws IOException {
            writeImpl();
        }
    
        private synchronized void writeImpl() throws IOException {
            if (completed) {
                return;
            }
            while (output.isReady()) {
                final byte[] message = getNextMessage();
                if (message == null) {
                    output.flush();
                    return;
                }
                output.write(message);
            }
        }
    
        private synchronized void completeImpl() {
            // also stops DataFeederThread to call bufferArrived
            completed = true;
            asyncContext.complete();
        }
    
        @Override
        public void onError(final Throwable t) {
            logger.error("Writer.onError", t);
            completeImpl();
        }
    
        public void dataArrived() {
            try {
                writeImpl();
            } catch (RuntimeException | IOException e) {
                ...
            }
        }
    
        public void noMoreData() {
            completeImpl();
        }
    
        @Override
        public synchronized void onComplete(final AsyncEvent event) throws IOException {
            completed = true; // might not needed but does not hurt
        }
    
        @Override
        public synchronized void onTimeout(final AsyncEvent event) throws IOException {
            completeImpl();
        }
    
        @Override
        public void onError(final AsyncEvent event) throws IOException {
            logger.error("onError", event.getThrowable());
        }
    
        ...
    }
    

    更新2018-08-01 :实际上它没有完全修复警告,请参见: “Closed while Pending/Unready” warnings from Jetty

        2
  •  2
  •   Joakim Erdfelt    8 年前

    您正在使用Servlet规范中的异步I/O并手动关闭响应流。

    关键事实:close调用意味着写操作。

    在Jetty案例中,IOException告诉您,对响应流close的手动调用导致了不希望的副作用。

    所以在异步I/O模式下 ServletOutputStream.isReady() 应该是在使用前做的 ServletOutputStream.close() isReady() 是真的。

    ServletOutputStream.close() AsyncContext.complete() 已经接到这封信了。

    但是,如果先前的书面文件尚未完成和/或 还没打电话来,这件具体的裸体案件 ServletOutputStream.close() 允许,但行为是中止响应,放弃 ServletOutputStream .

    这就是为什么你得到 IOException("Closed while Pending/Unready")

    现在你应该扪心自问为什么要关闭响应流?根据Servlet规范,这不是必需的,如果您想使用 RequestDispatcher 将多个servlet响应组合在一起。

    推荐文章