diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/AsyncContent.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/AsyncContent.java index 0ef54e2063d..932802c8df2 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/AsyncContent.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/AsyncContent.java @@ -49,12 +49,26 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable /** * {@inheritDoc} - *

The write completes when the {@link Content.Chunk} returned by {@link #read()} - * that wraps {@code byteBuffer} is released.

+ *

The write completes:

+ * */ @Override public void write(boolean last, ByteBuffer byteBuffer, Callback callback) { + // Since the contract is that the callback has to be succeeded when + // the chunk returned by read() is released, and since it is not + // possible to create chunks with no remaining byte, when the byte + // buffer is empty we need to replace it with EOF / EMPTY and cannot + // be notified about the release of the latter two. + // This is why read() succeeds the callback if it has no remaining + // byte, meaning it is either EOF or EMPTY. The callback is succeeded + // once and only once, but that happens either during read() if the + // byte buffer is empty or during Chunk.release() if it contains at + // least one byte. Content.Chunk chunk; if (byteBuffer.hasRemaining()) chunk = Content.Chunk.from(byteBuffer, last, callback::succeeded); @@ -68,6 +82,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable * write is complete.

*

The callback completes:

*