У нас есть асинхронный сервлет, который создает следующий журнал предупреждений от 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
.)
Я пробовал несколько вещей, чтобы реализовать образец асинхронного сервлета и клиента, но безуспешно. Я не думаю, что прикрепление этого кода слишком сильно поможет, но я могу это сделать, если кому-то интересно.
Версия причала: 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()
равно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()
WriteListener.onError()
a> и проверьте наличие ошибки записи. Это может быть ключом к источнику вашего ложногоclose()
. (ServletOutputStream.close()
вызывается после того, какonError()
сообщается приложению) - person Joakim Erdfelt   schedule 28.06.2018onError
с ведением журнала, но он не вызывается (я обновил вопрос с этим). - person palacsint   schedule 28.06.2018