-
Notifications
You must be signed in to change notification settings - Fork 2k
Use FileChannel.transferTo() to send static content #13631
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: jetty-12.1.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,7 @@ | |
import org.eclipse.jetty.io.internal.ContentSourceConsumer; | ||
import org.eclipse.jetty.io.internal.ContentSourceRetainableByteBuffer; | ||
import org.eclipse.jetty.io.internal.ContentSourceString; | ||
import org.eclipse.jetty.io.internal.Transferable; | ||
import org.eclipse.jetty.util.Blocker; | ||
import org.eclipse.jetty.util.BufferUtil; | ||
import org.eclipse.jetty.util.Callback; | ||
|
@@ -106,6 +107,38 @@ public static void copy(Source source, Sink sink, Chunk.Processor chunkProcessor | |
new ContentCopier(source, sink, chunkProcessor, callback).iterate(); | ||
} | ||
|
||
public static void copy(Source source, boolean last, Sink sink, Callback callback) | ||
{ | ||
new ContentCopier(source, last, sink, null, callback).iterate(); | ||
} | ||
|
||
private static void copyRange(Source source, long length, Sink sink, Callback callback) | ||
{ | ||
// TODO: it would be really difficult to make a source remember the bytes... | ||
// a subsequent call with the same source cannot have stored a chunk that | ||
// it returned previously, so do we really need a range? | ||
// Isn't the length always implicit to be the full length? | ||
// In HTTP/2 a large write is chunked and the write callback is not completed | ||
// until all the chunks are written (we store the BB in a DATA frame, and we | ||
// consume the BB chunk by chunk). | ||
// How can we do the same with a Source? | ||
// We can read a BB, wrap it in a DATA frame, even if larger than maxFrameSize | ||
// or flow control, as the Flusher will remember it. | ||
// But for transferTo(), we need a similar way for a Source to have position | ||
// and limit that a BB has, so perhaps we need a Source.Seekable. | ||
} | ||
|
||
public static boolean transfer(Source source, long length, Sink sink, Callback callback) | ||
{ | ||
if (source instanceof Transferable.From from) | ||
{ | ||
if (from.transferTo(sink, length, callback)) | ||
return true; | ||
} | ||
copyRange(source, length, sink, callback); | ||
return false; | ||
} | ||
|
||
/** | ||
* <p>A source of content that can be read with a read/demand model.</p> | ||
* <p>To avoid leaking its resources, a source <b>must</b> either:</p> | ||
|
@@ -175,6 +208,13 @@ interface Factory | |
Content.Source newContentSource(ByteBufferPool.Sized bufferPool, long offset, long length); | ||
} | ||
|
||
interface Aware | ||
{ | ||
Source getContentSource(); | ||
|
||
void setContentSource(Source source); | ||
} | ||
|
||
/** | ||
* Create a {@code Content.Source} from zero or more {@link ByteBuffer}s | ||
* @param byteBuffers The {@link ByteBuffer}s to use as the source. | ||
|
@@ -657,6 +697,8 @@ default boolean rewind() | |
*/ | ||
public interface Sink | ||
{ | ||
ByteBuffer TRANSFER = ByteBuffer.allocate(0); | ||
|
||
/** | ||
* <p>Wraps the given {@link OutputStream} as a {@link Sink}. | ||
* @param out The stream to wrap | ||
|
@@ -866,6 +908,36 @@ static void write(Sink sink, boolean last, String utf8Content, Callback callback | |
sink.write(last, ByteBuffer.wrap(utf8Content.getBytes(StandardCharsets.UTF_8)), callback); | ||
} | ||
|
||
static void write(Sink sink, boolean last, Content.Source source, Callback callback) | ||
{ | ||
Content.Source.Aware aware = findContentSourceAware(sink); | ||
if (aware != null) | ||
{ | ||
// Optimization to enable zero-copy. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Explain in the comment how the optimization still uses the sink.write path, so that commit logic etc. can be triggered. Specifically, we do not do |
||
aware.setContentSource(source); | ||
sink.write(last, TRANSFER, callback); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel that we should allow some failures on the callback here to fall through to a normal copy. I.e. if something in the implementation is Aware but cannot do a transferTo (perhaps because it would be non optimal) then it can fail this write with a |
||
} | ||
else | ||
{ | ||
// Normal source.read() + sink.write() full copy. | ||
Content.copy(source, last, sink, callback); | ||
} | ||
} | ||
|
||
private static Content.Source.Aware findContentSourceAware(Sink sink) | ||
{ | ||
while (true) | ||
{ | ||
if (sink instanceof Content.Source.Aware aware) | ||
return aware; | ||
if (sink instanceof Wrapper wrapper) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think if the sink is wrapped then we should not bypass it with transferTo |
||
sink = wrapper.getWrapped(); | ||
else | ||
break; | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* <p>Writes the given {@link ByteBuffer}, notifying the {@link Callback} | ||
* when the write is complete.</p> | ||
|
@@ -878,6 +950,27 @@ static void write(Sink sink, boolean last, String utf8Content, Callback callback | |
* @param callback the callback to notify when the write operation is complete | ||
*/ | ||
void write(boolean last, ByteBuffer byteBuffer, Callback callback); | ||
|
||
class Wrapper implements Sink | ||
{ | ||
private final Sink wrapped; | ||
|
||
public Wrapper(Sink wrapped) | ||
{ | ||
this.wrapped = wrapped; | ||
} | ||
|
||
public Sink getWrapped() | ||
{ | ||
return wrapped; | ||
} | ||
|
||
@Override | ||
public void write(boolean last, ByteBuffer byteBuffer, Callback callback) | ||
{ | ||
getWrapped().write(last, byteBuffer, callback); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #13656 as an alternate way to tunnel a Source through a ByteBuffer only API.
Although I'm hating the current iteration of Aware less than I did originally