我们有一个异步 servlet,它从 Jetty 生成以下警告日志:
java.io.IOException: Closed while Pending/Unready
启用调试日志后,我得到以下堆栈跟踪:
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
在客户端应用程序上使用来重现它。
如何使用示例 servlet 客户端代码重现此警告?我想在比我们的生产代码更简单的环境中理解这个问题,以便之后能够修复生产代码。示例 servlet 应该如何表现?是否可以在同一个 JUnit 测试中使用 Apache Commons HttpClient 客户端重现它?(这对于编写没有复杂网络黑客攻击的集成测试非常有用,例如kill -9
。)
我尝试了一些方法来实现示例异步 servlet 和客户端,但均未成功。我认为附加此代码不会有太大帮助,但如果有人感兴趣,我可以这样做。
码头版本: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();
}
...
}
}
可能的竞争条件:
- DataFeederThread: 调用
dataArrived()
->writeImpl()
,然后它得到的output.isReady()
是true
. onTimeout()
完成上下文的Jetty 调用。- 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();
}
onError
无论如何,日志消息周围没有错误日志Closed while Pending/Unready
(查看每个两个小时的时间范围),只有 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()