<div dir="ltr"><br><div>Thanks Chris.</div><div><br></div><div>Not sure whether this matters but the same bug/code goes back all the way to JDK 11... (the issue lists JDK 17+ as affected):</div><div><br></div><div><a href="https://github.com/openjdk/jdk11u/blob/master/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java#L207-L215">https://github.com/openjdk/jdk11u/blob/master/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java#L207-L215</a><br></div><div><br></div><div>Dawid</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Dec 19, 2022 at 10:23 AM Chris Hegarty <<a href="mailto:chegar999@gmail.com">chegar999@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Hi Dawid,<br>
<br>
Thanks for raising this issue. I filed the following JIRA to track it:<br>
<a href="https://bugs.openjdk.org/browse/JDK-8299015" rel="noreferrer" target="_blank">https://bugs.openjdk.org/browse/JDK-8299015</a><br>
<br>
-Chris.<br>
<br>
On 17/12/2022 18:45, Dawid Weiss wrote:<br>
> Hello,<br>
> <br>
> I've discovered a bug in the implementation of PathSubscriber in how<br>
> byte buffers are flushed/ written to the file channel. Specifically,<br>
> this method in PathSubscriber [2]:<br>
> <br>
> @Override<br>
> public void onNext(List<ByteBuffer> items) {<br>
> try {<br>
> out.write(items.toArray(Utils.EMPTY_BB_ARRAY));<br>
> ...<br>
> <br>
> the implementation assumes out.write(ByteBuffer[]) always writes the<br>
> content of all buffers - this isn't the case [1] and the method can<br>
> write any number of bytes from input buffers.<br>
> <br>
> The manifestation of this bug is that HttpClient can fail to write all<br>
> the data in a heavily chunked http response (!).<br>
> <br>
> The longer story is that I discovered the above while attempting to<br>
> debug data inconsistency errors in a HttpClient fetching chunked<br>
> responses. I'm on Windows and the problem is fairly easy to reproduce.<br>
> Wireshark (packet interceptor) shows full data being transmitted,<br>
> HttpClient fails to write all of it to disk with:<br>
> <br>
> httpClient.send(request, HttpResponse.BodyHandlers.ofFile(outputFile));<br>
> <br>
> The bug is fairly evident - I'm not sure how it slipped undetected for<br>
> so long. The non-exhaustive FileChannel.write(ByteBuffer[]) behavior<br>
> can be demonstrated by this code:<br>
> <br>
> var file = Files.createTempFile("tmp", "tmp");<br>
> try (var out = FileChannel.open(file, StandardOpenOption.WRITE,<br>
> StandardOpenOption.CREATE)) {<br>
> // Create a lot of smallish buffers.<br>
> ByteBuffer[] buffers = new ByteBuffer[100];<br>
> for (int i = 0; i < buffers.length; i++) {<br>
> buffers[i] = ByteBuffer.wrap(new byte[100]);<br>
> }<br>
> <br>
> System.out.println(<br>
> "Data in buffers: " +<br>
> Arrays.stream(buffers).mapToLong(Buffer::remaining).sum());<br>
> long len = out.write(buffers);<br>
> System.out.println("Written: " + len);<br>
> System.out.println(<br>
> "Data in buffers: " +<br>
> Arrays.stream(buffers).mapToLong(Buffer::remaining).sum());<br>
> } finally {<br>
> Files.delete(file);<br>
> }<br>
> <br>
> This prints the following for me on Windows:<br>
> <br>
> Data in buffers: 10000<br>
> Written: 1600<br>
> Data in buffers: 8400<br>
> <br>
> Clearly, PathSubscriber should have a loop to exhaust all buffers.<br>
> Seems like a trivial fix, so I don't include the patch (and I can't<br>
> file a proper Jira issue because I have no access to Jira).<br>
> <br>
> Dawid<br>
> <br>
> [1] <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/channels/FileChannel.html#write(java.nio.ByteBuffer%5B%5D)" rel="noreferrer" target="_blank">https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/channels/FileChannel.html#write(java.nio.ByteBuffer%5B%5D)</a><br>
> <br>
> [2] <a href="https://github.com/openjdk/jdk/blob/master/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java#L285" rel="noreferrer" target="_blank">https://github.com/openjdk/jdk/blob/master/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java#L285</a><br>
</blockquote></div>