The last zero-chunk in Transfer-encoding: chunked response can be missing when Undertow write-timeout is reached
Environment
- Red Hat JBoss Enterprise Application Platform (EAP)
- 7.x
Issue
- HTTP Long polling is implemented with asynchronous servlet API, the last chunk
0\r\n\r\nwill be missing when the polling interval is longer than Undertowwrite-timeout. - Getting a
net::ERR_INCOMPLETE_CHUNKED_ENCODINGerror in the browser console if the browser is chrome.
Resolution
This is a known issue as reported This content is not included.JBEAP-23722, fixed in JBoss EAP 7.4.12+. As a workaround, there are two options:
- Set write-timeout longer than long-polling interval, or unset
write-timeoutin http-listener.write-timeoutis not set by default. - Comment out calling
flush()from the servlet. If response type isContent-length: <byte>, this issue does not occur.
Root Cause
In the current Undertow implementation of write-timeout, when requests that re-use a keep-alive connection, write-timeout set during the previous request processing is taken over to the next request. As a result, the last zero chunk truncated response is returned if meet the following conditions are true:
- The request re-use a keep-alive connection.
- The request takes longer than
write-timeoutlike long-polling processing by AsyncServlet. Transfer-Encoding: chunkedis applied to the response by callingflush()in the servlet.
Diagnostic Steps
Attach the following Byteman rule:
RULE trace createLastChunk
CLASS io.undertow.conduits.ChunkedStreamSinkConduit
METHOD createLastChunk(boolean)
AT ENTRY
IF true
DO traceStack("### Hit BYTEMAN - createLastChunk $1 = " + $1 + " ");
ENDRULE
RULE trace closing channel
CLASS sun.nio.ch.SocketDispatcher
METHOD close(java.io.FileDescriptor)
AT ENTRY
IF true
DO traceStack("### SocketChannel is closing now ");
ENDRULE
RULE trace updating expireTime in handleWriteTimeout
CLASS io.undertow.conduits.WriteTimeoutStreamSinkConduit
METHOD handleWriteTimeout(long)
AFTER WRITE expireTime
IF true
DO traceStack("### Hit BYTEMAN updated expireTime = " + $0.expireTime + " ");
ENDRULE
RULE trace updating expireTime in handleResumeTimeout
CLASS io.undertow.conduits.WriteTimeoutStreamSinkConduit
METHOD handleResumeTimeout()
AFTER WRITE expireTime
IF true
DO traceStack("### Hit BYTEMAN updated expireTime = " + $0.expireTime + " ");
ENDRULE
Add the following java options in JBOSS_HOME/bin/standalone.conf to attach byteman:
BYTEMAN_HOME=/<path-to-byteman>/byteman-download-4.0.16 <<<===
if [ "x$JAVA_OPTS" = "x" ]; then
JAVA_OPTS="-Xms1303m -Xmx1303m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true"
JAVA_OPTS="$JAVA_OPTS -Djboss.modules.system.pkgs=$JBOSS_MODULES_SYSTEM_PKGS -Djava.awt.headless=true"
JAVA_OPTS="$JAVA_OPTS -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:${BYTEMAN_HOME}/scripts/TraceLastChunk.btm,boot:${BYTEMAN_HOME}/lib/byteman.jar" <<<===
else
echo "JAVA_OPTS already set in environment; overriding default settings with values: $JAVA_OPTS"
fi
The following log is an example of JBoss EAP 7.4.5 with OpenJDK8 if it corresponds to this issue. If the socket is closed on the stack above from WriteTimeoutStreamSinkConduit and createLastChunk is called afterward, the last zero chunk is missing due to write-timeout:
18:54:06,753 INFO [stdout] (default task-1) ### SocketChannel is closing now sun.nio.ch.SocketDispatcher.close(SocketDispatcher.java:-1)
18:54:06,753 INFO [stdout] (default task-1) sun.nio.ch.SocketChannelImpl.kill(SocketChannelImpl.java:878)
18:54:06,753 INFO [stdout] (default task-1) sun.nio.ch.SocketChannelImpl.implCloseSelectableChannel(SocketChannelImpl.java:860)
18:54:06,754 INFO [stdout] (default task-1) java.nio.channels.spi.AbstractSelectableChannel.implCloseChannel(AbstractSelectableChannel.java:241)
18:54:06,754 INFO [stdout] (default task-1) java.nio.channels.spi.AbstractInterruptibleChannel.close(AbstractInterruptibleChannel.java:115)
18:54:06,754 INFO [stdout] (default task-1) org.xnio.nio.NioSocketStreamConnection.closeAction(NioSocketStreamConnection.java:135)
18:54:06,754 INFO [stdout] (default task-1) org.xnio.Connection.close(Connection.java:132)
18:54:06,754 INFO [stdout] (default task-1) org.xnio.IoUtils.safeClose(IoUtils.java:152)
18:54:06,754 INFO [stdout] (default task-1) io.undertow.conduits.WriteTimeoutStreamSinkConduit.handleWriteTimeout(WriteTimeoutStreamSinkConduit.java:101)
18:54:06,754 INFO [stdout] (default task-1) io.undertow.conduits.WriteTimeoutStreamSinkConduit.write(WriteTimeoutStreamSinkConduit.java:117)
...
18:54:06,755 INFO [stdout] (default task-1) io.undertow.servlet.spec.ServletPrintWriterDelegate.flush(ServletPrintWriterDelegate.java:76)
18:54:06,755 INFO [stdout] (default task-1) test.LongPollingServlet$MyListener.onTimeout(LongPollingServlet.java:27)
...
18:54:06,758 INFO [stdout] (default task-1) ### Hit BYTEMAN - createLastChunk $1 = false io.undertow.conduits.ChunkedStreamSinkConduit.createLastChunk(ChunkedStreamSinkConduit.java:-1)
18:54:06,758 INFO [stdout] (default task-1) io.undertow.conduits.ChunkedStreamSinkConduit.terminateWrites(ChunkedStreamSinkConduit.java:318)
18:54:06,758 INFO [stdout] (default task-1) org.xnio.conduits.ConduitStreamSinkChannel.shutdownWrites(ConduitStreamSinkChannel.java:178)
18:54:06,758 INFO [stdout] (default task-1) org.xnio.StreamConnection.notifyWriteClosed(StreamConnection.java:95)
18:54:06,758 INFO [stdout] (default task-1) org.xnio.nio.NioSocketStreamConnection.notifyWriteClosed(NioSocketStreamConnection.java:145)
18:54:06,758 INFO [stdout] (default task-1) org.xnio.Connection.close(Connection.java:135)
18:54:06,758 INFO [stdout] (default task-1) org.xnio.IoUtils.safeClose(IoUtils.java:152)
18:54:06,758 INFO [stdout] (default task-1) io.undertow.conduits.WriteTimeoutStreamSinkConduit.handleWriteTimeout(WriteTimeoutStreamSinkConduit.java:101)
18:54:06,758 INFO [stdout] (default task-1) io.undertow.conduits.WriteTimeoutStreamSinkConduit.write(WriteTimeoutStreamSinkConduit.java:117)
...
This solution is part of Red Hat’s fast-track publication program, providing a huge library of solutions that Red Hat engineers have created while supporting our customers. To give you the knowledge you need the instant it becomes available, these articles may be presented in a raw and unedited form.