IOResources#toRetainableByteBuffer data loss when using resources without path (original) (raw)

Jetty version(s)
12.1.9

Jetty Environment
core

HTTP version
not-relevant

Java version/vendor (use: java -version)
17

OS type/version
any

Description
I'm working on https://github.com/ops4j/org.ops4j.pax.web project which implements OSGi CMPN web-related specifications consistently for Jetty, Tomcat and Undertow servers.

We had a weird issues visible at this stack trace:

 - OSGi - 13:47:40.526 [qtp1791035325-54] WARN  (Response.java:652) org.eclipse.jetty.server.Response - writeError: status=500, message=java.io.IOException: written 872337 < 872817 content-length, response=oejsi.HttpChannelState$ErrorResponse@6d8fd6b7{500,GET@6a85c2db http://127.0.0.1:8181/documentation/ HTTP/1.1}
java.io.IOException: written 872337 < 872817 content-length
    at org.eclipse.jetty.server.internal.HttpChannelState$ChannelResponse.write(HttpChannelState.java:1385)
    at org.eclipse.jetty.server.Response$Wrapper.write(Response.java:841)
    at org.eclipse.jetty.server.handler.ContextResponse.write(ContextResponse.java:56)
    at org.eclipse.jetty.ee8.nested.HttpChannel.send(HttpChannel.java:851)
    at org.eclipse.jetty.ee8.nested.HttpChannel.sendResponse(HttpChannel.java:831)
    at org.eclipse.jetty.ee8.nested.HttpChannel.write(HttpChannel.java:905)
    at org.eclipse.jetty.ee8.nested.HttpOutput.channelWrite(HttpOutput.java:298)
    at org.eclipse.jetty.ee8.nested.HttpOutput.lambda$sendContent$1(HttpOutput.java:1140)
    at org.eclipse.jetty.http.content.CachingHttpContentFactory$CachedHttpContent.writeTo(CachingHttpContentFactory.java:391)
    at org.eclipse.jetty.ee8.nested.HttpOutput.sendContent(HttpOutput.java:1142)
    at org.eclipse.jetty.ee8.nested.HttpOutput.sendContent(HttpOutput.java:1033)
    at org.eclipse.jetty.ee8.nested.ResourceService.sendData(ResourceService.java:612)
    at org.eclipse.jetty.ee8.nested.ResourceService.doGet(ResourceService.java:281)
    at org.ops4j.pax.web.service.jetty.internal.web.DefaultServlet.doGet(DefaultServlet.java:478)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:497)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:584)
...

Pax Web configures Jetty Default/Resource servlet to serve static resources, which may come from OSGi bundles (so Jetty URL Resources with bundle: scheme are used).

I've finally reproduced the issue without any OSGi code and narrowed down the problem to org.eclipse.jetty.io.RetainableByteBuffer.DynamicCapacity#append() method - it doesn't mark the passed org.eclipse.jetty.io.RetainableByteBuffer as retained, so the buffer is reused in the loop inside org.eclipse.jetty.io.IOResources#toRetainableByteBuffer().
The problem is here:

int read = inputStream.read(buffer.getByteBuffer().array());

java.nio.ByteBuffer#array() is passed to read operation, however the aggregate buffer may already have non-zero java.nio.Buffer#position(), so even if some data is read, java.nio.Buffer#remaining() returns zero (as the position may exceed the limit).

How to reproduce?
See the updated org.eclipse.jetty.io.IOResourcesTest#testToRetainableByteBuffer() in this PR: