У нас есть асинхронный сервлет, который выдает следующий журнал предупреждений от 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
в клиентском приложении.
Как я могу воспроизвести это предупреждение с примером кода сервлета-клиента?Я хотел бы понять эту проблему в более простой среде, чем наш производственный код, чтобы потом можно было исправить производственный код.Как должен вести себя образец сервлета?Можно ли воспроизвести это на стороне клиента Apache Commons HttpClient в том же тесте JUnit?(Это было бы здорово для написания интеграционного теста без сложного взлома сети, например kill -9
.)
Я несколько раз пытался реализовать пример асинхронного сервлета и клиента без успеха.Я не думаю, что присоединение этого кода поможет слишком много, но я могу сделать это, если кому-то интересно.
Версия Jetty: 9.4.8.v20171121
обновление (2018-06-27):
Обращаясь к полезному ответу @Joakim Erdfelt, я не нашел ни одного вызова close()
в нашем коде, но обнаружил подозрительную пропущенную синхронизацию.Вот база нашего сервлета асинхронного опроса:
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()
is true
. - Jetty вызывает
onTimeout()
, что завершает контекст. - 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()