You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2017/10/31 17:45:30 UTC
svn commit: r1813918 - in /tomcat/trunk: java/org/apache/coyote/http2/
webapps/docs/
Author: remm
Date: Tue Oct 31 17:45:30 2017
New Revision: 1813918
URL: http://svn.apache.org/viewvc?rev=1813918&view=rev
Log:
Add sendfile support for HTTP/2 NIO2 using mapped files. Performance seems to be improved with h2load.
Modified:
tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java
tomcat/trunk/java/org/apache/coyote/http2/Stream.java
tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java
tomcat/trunk/webapps/docs/changelog.xml
Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java?rev=1813918&r1=1813917&r2=1813918&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java Tue Oct 31 17:45:30 2017
@@ -16,9 +16,14 @@
*/
package org.apache.coyote.http2;
+import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
import java.nio.channels.CompletionHandler;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -27,8 +32,12 @@ import org.apache.coyote.Adapter;
import org.apache.coyote.ProtocolException;
import org.apache.coyote.Request;
import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.net.SendfileState;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.net.SocketWrapperBase.BlockingMode;
+import org.apache.tomcat.util.net.SocketWrapperBase.CompletionCheck;
+import org.apache.tomcat.util.net.SocketWrapperBase.CompletionHandlerCall;
+import org.apache.tomcat.util.net.SocketWrapperBase.CompletionState;
public class Http2AsyncUpgradeHandler extends Http2UpgradeHandler {
@@ -69,6 +78,11 @@ public class Http2AsyncUpgradeHandler ex
}
@Override
+ boolean hasAsyncIO() {
+ return true;
+ }
+
+ @Override
protected void writeSettings() {
// Send the initial settings frame
socketWrapper.write(BlockingMode.SEMI_BLOCK, protocol.getWriteTimeout(),
@@ -230,6 +244,159 @@ public class Http2AsyncUpgradeHandler ex
}
}
+ @Override
+ protected void processWrites() throws IOException {
+ if (socketWrapper.isWritePending()) {
+ socketWrapper.registerWriteInterest();
+ }
+ }
+
+ @Override
+ protected SendfileState processSendfile(Stream stream) {
+ String fileName = (String) stream.getCoyoteRequest().getAttribute(
+ org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
+ if (fileName != null) {
+ java.nio.file.Path path = new File(fileName).toPath();
+ SendfileData sendfile = new SendfileData();
+ sendfile.pos = ((Long) stream.getCoyoteRequest().getAttribute(
+ org.apache.coyote.Constants.SENDFILE_FILE_START_ATTR)).longValue();
+ sendfile.end = ((Long) stream.getCoyoteRequest().getAttribute(
+ org.apache.coyote.Constants.SENDFILE_FILE_END_ATTR)).longValue();
+ sendfile.left = sendfile.end - sendfile.pos;
+ try {
+ try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
+ sendfile.mappedBuffer = channel.map(MapMode.READ_ONLY, sendfile.pos, sendfile.end - sendfile.pos);
+ sendfile.stream = stream;
+ }
+ // Reserve as much as possible right away
+ int reservation = (sendfile.end - sendfile.pos > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) (sendfile.end - sendfile.pos);
+ sendfile.streamReservation = stream.reserveWindowSize(reservation, true);
+ sendfile.connectionReservation = reserveWindowSize(stream, sendfile.streamReservation);
+ } catch (IOException e) {
+ return SendfileState.ERROR;
+ }
+ // Actually perform the write
+ int frameSize = Integer.min(getMaxFrameSize(), sendfile.connectionReservation);
+ boolean finished = (frameSize == sendfile.left) && stream.getCoyoteResponse().getTrailerFields() == null;
+
+ // Need to check this now since sending end of stream will change this.
+ boolean writeable = stream.canWrite();
+ byte[] header = new byte[9];
+ ByteUtil.setThreeBytes(header, 0, frameSize);
+ header[3] = FrameType.DATA.getIdByte();
+ if (finished) {
+ header[4] = FLAG_END_OF_STREAM;
+ stream.sentEndOfStream();
+ if (!stream.isActive()) {
+ activeRemoteStreamCount.decrementAndGet();
+ }
+ }
+ if (writeable) {
+ ByteUtil.set31Bits(header, 5, stream.getIdentifier().intValue());
+ sendfile.mappedBuffer.limit(sendfile.mappedBuffer.position() + frameSize);
+ socketWrapper.write(BlockingMode.SEMI_BLOCK, protocol.getWriteTimeout(),
+ TimeUnit.MILLISECONDS, sendfile, COMPLETE_WRITE_WITH_COMPLETION,
+ new SendfileCompletionHandler(), ByteBuffer.wrap(header), sendfile.mappedBuffer);
+ try {
+ handleAsyncException();
+ } catch (IOException e) {
+ return SendfileState.ERROR;
+ }
+ }
+ return SendfileState.PENDING;
+ } else {
+ return SendfileState.DONE;
+ }
+ }
+
+ private static final CompletionCheck COMPLETE_WRITE_WITH_COMPLETION = new CompletionCheck() {
+ @Override
+ public CompletionHandlerCall callHandler(CompletionState state, ByteBuffer[] buffers,
+ int offset, int length) {
+ for (int i = 0; i < length; i++) {
+ if (buffers[offset + i].remaining() > 0) {
+ return CompletionHandlerCall.CONTINUE;
+ }
+ }
+ return CompletionHandlerCall.DONE;
+ }
+ };
+
+ protected class SendfileCompletionHandler implements CompletionHandler<Long, SendfileData> {
+ @Override
+ public void completed(Long nBytes, SendfileData sendfile) {
+ long bytesWritten = nBytes.longValue() - 9;
+ sendfile.left -= bytesWritten;
+ if (sendfile.left == 0) {
+ try {
+ sendfile.stream.getOutputBuffer().close();
+ } catch (IOException e) {
+ failed(e, sendfile);
+ }
+ return;
+ }
+ sendfile.streamReservation -= bytesWritten;
+ sendfile.connectionReservation -= bytesWritten;
+ sendfile.pos += bytesWritten;
+ try {
+ if (sendfile.connectionReservation == 0) {
+ if (sendfile.streamReservation == 0) {
+ int reservation = (sendfile.end - sendfile.pos > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) (sendfile.end - sendfile.pos);
+ sendfile.streamReservation = sendfile.stream.reserveWindowSize(reservation, true);
+ }
+ sendfile.connectionReservation = reserveWindowSize(sendfile.stream, sendfile.streamReservation);
+ }
+ } catch (IOException e) {
+ failed (e, sendfile);
+ return;
+ }
+ int frameSize = Integer.min(getMaxFrameSize(), sendfile.streamReservation);
+ boolean finished = (frameSize == sendfile.left) && sendfile.stream.getCoyoteResponse().getTrailerFields() == null;
+
+ // Need to check this now since sending end of stream will change this.
+ boolean writeable = sendfile.stream.canWrite();
+ byte[] header = new byte[9];
+ ByteUtil.setThreeBytes(header, 0, frameSize);
+ header[3] = FrameType.DATA.getIdByte();
+ if (finished) {
+ header[4] = FLAG_END_OF_STREAM;
+ sendfile.stream.sentEndOfStream();
+ if (!sendfile.stream.isActive()) {
+ activeRemoteStreamCount.decrementAndGet();
+ }
+ }
+ if (writeable) {
+ ByteUtil.set31Bits(header, 5, sendfile.stream.getIdentifier().intValue());
+ sendfile.mappedBuffer.limit(sendfile.mappedBuffer.position() + frameSize);
+ socketWrapper.write(BlockingMode.SEMI_BLOCK, protocol.getWriteTimeout(),
+ TimeUnit.MILLISECONDS, sendfile, COMPLETE_WRITE_WITH_COMPLETION,
+ this, ByteBuffer.wrap(header), sendfile.mappedBuffer);
+ try {
+ handleAsyncException();
+ } catch (IOException e) {
+ failed(e, sendfile);
+ }
+ }
+ }
+
+ @Override
+ public void failed(Throwable t, SendfileData sendfile) {
+ applicationErrorCompletion.failed(t, null);
+ }
+ }
+
+ protected class SendfileData {
+ protected Stream stream;
+ // Note: a mapped buffer is a special construct with an underlying file
+ // that doesn't need to be closed
+ protected MappedByteBuffer mappedBuffer;
+ protected int frameSize;
+ protected long left;
+ protected int streamReservation;
+ protected int connectionReservation;
+ protected long pos;
+ protected long end;
+ }
protected class AsyncPingManager extends PingManager {
@Override
Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java?rev=1813918&r1=1813917&r2=1813918&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Tue Oct 31 17:45:30 2017
@@ -50,6 +50,7 @@ import org.apache.tomcat.util.codec.bina
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.SendfileState;
import org.apache.tomcat.util.net.SocketEvent;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.res.StringManager;
@@ -699,7 +700,12 @@ class Http2UpgradeHandler extends Abstra
}
- private void processWrites() throws IOException {
+ boolean hasAsyncIO() {
+ return false;
+ }
+
+
+ protected void processWrites() throws IOException {
synchronized (socketWrapper) {
if (socketWrapper.flush(false)) {
socketWrapper.registerWriteInterest();
@@ -799,6 +805,10 @@ class Http2UpgradeHandler extends Abstra
}
+ protected SendfileState processSendfile(Stream stream) {
+ return SendfileState.DONE;
+ }
+
private synchronized Set<AbstractStream> releaseBackLog(int increment) {
Set<AbstractStream> result = new HashSet<>();
if (backLogSize < increment) {
Modified: tomcat/trunk/java/org/apache/coyote/http2/Stream.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Stream.java?rev=1813918&r1=1813917&r2=1813918&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Stream.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Stream.java Tue Oct 31 17:45:30 2017
@@ -104,8 +104,7 @@ class Stream extends AbstractStream impl
// TODO Assuming the body has been read at this point is not valid
state.receivedEndOfStream();
}
- // No sendfile for HTTP/2 (it is enabled by default in the request)
- this.coyoteRequest.setSendfile(false);
+ this.coyoteRequest.setSendfile(handler.hasAsyncIO());
this.coyoteResponse.setOutputBuffer(outputBuffer);
this.coyoteRequest.setResponse(coyoteResponse);
this.coyoteRequest.protocol().setString("HTTP/2.0");
@@ -198,7 +197,7 @@ class Stream extends AbstractStream impl
}
- private final synchronized int reserveWindowSize(int reservation, boolean block)
+ final synchronized int reserveWindowSize(int reservation, boolean block)
throws IOException {
long windowSize = getWindowSize();
while (windowSize < 1) {
Modified: tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java?rev=1813918&r1=1813917&r2=1813918&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java Tue Oct 31 17:45:30 2017
@@ -30,6 +30,7 @@ import org.apache.juli.logging.LogFactor
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
import org.apache.tomcat.util.net.DispatchType;
+import org.apache.tomcat.util.net.SendfileState;
import org.apache.tomcat.util.net.SocketEvent;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.res.StringManager;
@@ -41,6 +42,7 @@ class StreamProcessor extends AbstractPr
private final Http2UpgradeHandler handler;
private final Stream stream;
+ private SendfileState sendfileState = null;
StreamProcessor(Http2UpgradeHandler handler, Stream stream, Adapter adapter,
@@ -102,7 +104,10 @@ class StreamProcessor extends AbstractPr
@Override
protected final void finishResponse() throws IOException {
- stream.getOutputBuffer().close();
+ sendfileState = handler.processSendfile(stream);
+ if (!(sendfileState == SendfileState.PENDING)) {
+ stream.getOutputBuffer().close();
+ }
}
@@ -261,7 +266,9 @@ class StreamProcessor extends AbstractPr
setErrorState(ErrorState.CLOSE_NOW, e);
}
- if (getErrorState().isError()) {
+ if (sendfileState == SendfileState.PENDING) {
+ return SocketState.SENDFILE;
+ } else if (getErrorState().isError()) {
action(ActionCode.CLOSE, null);
request.updateCounters();
return SocketState.CLOSED;
Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1813918&r1=1813917&r2=1813918&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Tue Oct 31 17:45:30 2017
@@ -77,6 +77,9 @@
<fix>
Improve NIO2 syncing for async IO operations. (remm)
</fix>
+ <add>
+ Sendfile support for HTTP/2 and NIO2. (remm)
+ </add>
</changelog>
</subsection>
<subsection name="Jasper">
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org