Author: remm Date: Thu Jul 10 20:21:55 2014 New Revision: 1609564 URL: http://svn.apache.org/r1609564 Log: Fix various NIO2 sendfile issues discovered while testing ciphers, and refactor to optimize its behavior.
Modified: tomcat/trunk/java/org/apache/coyote/http11/Http11Nio2Processor.java tomcat/trunk/java/org/apache/tomcat/util/net/Nio2Endpoint.java tomcat/trunk/webapps/docs/changelog.xml Modified: tomcat/trunk/java/org/apache/coyote/http11/Http11Nio2Processor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/Http11Nio2Processor.java?rev=1609564&r1=1609563&r2=1609564&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/Http11Nio2Processor.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/Http11Nio2Processor.java Thu Jul 10 20:21:55 2014 @@ -280,17 +280,21 @@ public class Http11Nio2Processor extends if (sendfileData != null && !getErrorState().isError()) { ((Nio2Endpoint.Nio2SocketWrapper) socketWrapper).setSendfileData(sendfileData); sendfileData.keepAlive = keepAlive; - if (((Nio2Endpoint) endpoint).processSendfile( - (Nio2Endpoint.Nio2SocketWrapper) socketWrapper)) { - sendfileInProgress = true; - } else { + switch (((Nio2Endpoint) endpoint) + .processSendfile((Nio2Endpoint.Nio2SocketWrapper) socketWrapper)) { + case DONE: + return false; + case ERROR: // Write failed if (log.isDebugEnabled()) { log.debug(sm.getString("http11processor.sendfile.error")); } setErrorState(ErrorState.CLOSE_NOW, null); + return true; + case PENDING: + sendfileInProgress = true; + return true; } - return true; } return false; } Modified: tomcat/trunk/java/org/apache/tomcat/util/net/Nio2Endpoint.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/Nio2Endpoint.java?rev=1609564&r1=1609563&r2=1609564&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/net/Nio2Endpoint.java (original) +++ tomcat/trunk/java/org/apache/tomcat/util/net/Nio2Endpoint.java Thu Jul 10 20:21:55 2014 @@ -17,11 +17,11 @@ package org.apache.tomcat.util.net; +import java.io.EOFException; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.net.StandardSocketOptions; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousChannelGroup; import java.nio.channels.AsynchronousServerSocketChannel; @@ -315,8 +315,6 @@ public class Nio2Endpoint extends Abstra // Determine which cipher suites and protocols to enable enabledCiphers = sslUtil.getEnableableCiphers(sslContext); enabledProtocols = sslUtil.getEnableableProtocols(sslContext); - // FIXME: temporary, investigate apparent sendfile issue - useSendfile = false; } if (oomParachute>0) reclaimParachute(true); @@ -888,7 +886,85 @@ public class Nio2Endpoint extends Abstra TimeUnit.MILLISECONDS, socket, awaitBytes); } - public boolean processSendfile(final Nio2SocketWrapper socket) { + public enum SendfileState { + PENDING, DONE, ERROR + } + + private CompletionHandler<Integer, SendfileData> sendfile = new CompletionHandler<Integer, SendfileData>() { + + @Override + public void completed(Integer nWrite, SendfileData attachment) { + if (nWrite.intValue() < 0) { // Reach the end of stream + failed(new EOFException(), attachment); + return; + } + attachment.pos += nWrite.intValue(); + if (!attachment.buffer.hasRemaining()) { + if (attachment.length <= 0) { + // All data has now been written + attachment.socket.setSendfileData(null); + attachment.buffer.clear(); + try { + attachment.fchannel.close(); + } catch (IOException e) { + // Ignore + } + if (attachment.keepAlive) { + if (!isInline()) { + awaitBytes(attachment.socket); + } else { + attachment.doneInline = true; + } + } else { + if (!isInline()) { + processSocket(attachment.socket, SocketStatus.DISCONNECT, false); + } else { + attachment.doneInline = true; + } + } + return; + } else { + attachment.buffer.clear(); + int nRead = -1; + try { + nRead = attachment.fchannel.read(attachment.buffer); + } catch (IOException e) { + failed(e, attachment); + return; + } + if (nRead > 0) { + attachment.buffer.flip(); + if (attachment.length < attachment.buffer.remaining()) { + attachment.buffer.limit(attachment.buffer.limit() - attachment.buffer.remaining() + (int) attachment.length); + } + attachment.length -= nRead; + } else { + failed(new EOFException(), attachment); + return; + } + } + } + attachment.socket.getSocket().write(attachment.buffer, attachment.socket.getTimeout(), + TimeUnit.MILLISECONDS, attachment, this); + } + + @Override + public void failed(Throwable exc, SendfileData attachment) { + try { + attachment.fchannel.close(); + } catch (IOException e) { + // Ignore + } + if (!isInline()) { + processSocket(attachment.socket, SocketStatus.ERROR, false); + } else { + attachment.doneInline = true; + attachment.error = true; + } + } + }; + + public SendfileState processSendfile(Nio2SocketWrapper socket) { // Configure the send file data SendfileData data = socket.getSendfileData(); @@ -898,125 +974,41 @@ public class Nio2Endpoint extends Abstra data.fchannel = java.nio.channels.FileChannel .open(path, StandardOpenOption.READ).position(data.pos); } catch (IOException e) { - closeSocket(socket, SocketStatus.ERROR); - return false; - } - } - - final ByteBuffer buffer; - if (!socketProperties.getDirectBuffer() && sslContext == null) { - // If not using SSL and direct buffers are not used, the - // idea of sendfile is to avoid memory copies, so allocate a - // direct buffer - int bufferSize; - try { - Integer bufferSizeInteger = socket.getSocket().getIOChannel().getOption(StandardSocketOptions.SO_SNDBUF); - if (bufferSizeInteger != null) { - bufferSize = bufferSizeInteger.intValue(); - } else { - bufferSize = 8192; - } - } catch (IOException e) { - bufferSize = 8192; + return SendfileState.ERROR; } - buffer = ByteBuffer.allocateDirect(bufferSize); - } else { - buffer = socket.getSocket().getBufHandler().getWriteBuffer(); } - int nr = -1; + ByteBuffer buffer = socket.getSocket().getBufHandler().getWriteBuffer(); + buffer.clear(); + int nRead = -1; try { - nr = data.fchannel.read(buffer); + nRead = data.fchannel.read(buffer); } catch (IOException e1) { - closeSocket(socket, SocketStatus.ERROR); - return false; + return SendfileState.ERROR; } - if (nr >= 0) { + if (nRead >= 0) { buffer.flip(); - socket.getSocket().write(buffer, data, new CompletionHandler<Integer, SendfileData>() { - - @Override - public void completed(Integer nw, SendfileData attachment) { - if (nw.intValue() < 0) { // Reach the end of stream - closeSocket(socket, SocketStatus.DISCONNECT); - try { - attachment.fchannel.close(); - } catch (IOException e) { - // Ignore - } - return; - } - - attachment.pos += nw.intValue(); - attachment.length -= nw.intValue(); - - if (attachment.length <= 0) { - socket.setSendfileData(null); - try { - attachment.fchannel.close(); - } catch (IOException e) { - // Ignore - } - if (attachment.keepAlive) { - awaitBytes(socket); - } else { - closeSocket(socket, SocketStatus.DISCONNECT); - } - return; - } - - boolean ok = true; - - if (!buffer.hasRemaining()) { - // This means that all data in the buffer has - // been - // written => Empty the buffer and read again - buffer.clear(); - try { - if (attachment.fchannel.read(buffer) >= 0) { - buffer.flip(); - if (attachment.length < buffer.remaining()) { - buffer.limit(buffer.limit() - buffer.remaining() + (int) attachment.length); - } - } else { - // Reach the EOF - ok = false; - } - } catch (Throwable th) { - ExceptionUtils.handleThrowable(th); - if (log.isDebugEnabled()) { - log.debug(sm.getString("endpoint.sendfile.error"), th); - } - ok = false; - } - } - - if (ok) { - socket.getSocket().write(buffer, attachment, this); - } else { - try { - attachment.fchannel.close(); - } catch (IOException e) { - // Ignore - } - closeSocket(socket, SocketStatus.ERROR); - } - } - - @Override - public void failed(Throwable exc, SendfileData attachment) { - // Closing channels - closeSocket(socket, SocketStatus.ERROR); - try { - attachment.fchannel.close(); - } catch (IOException e) { - // Ignore - } + data.socket = socket; + data.buffer = buffer; + data.length -= nRead; + startInline(); + try { + socket.getSocket().write(buffer, socket.getTimeout(), TimeUnit.MILLISECONDS, + data, sendfile); + } finally { + endInline(); + } + if (data.doneInline) { + if (data.error) { + return SendfileState.ERROR; + } else { + return SendfileState.DONE; } - }); - return true; + } else { + return SendfileState.PENDING; + } } else { - return false; + return SendfileState.ERROR; } } @@ -1172,5 +1164,10 @@ public class Nio2Endpoint extends Abstra public long length; // KeepAlive flag public boolean keepAlive; + // Internal use only + private Nio2SocketWrapper socket; + private ByteBuffer buffer; + private boolean doneInline = false; + private boolean error = false; } } Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1609564&r1=1609563&r2=1609564&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Thu Jul 10 20:21:55 2014 @@ -44,6 +44,25 @@ They eventually become mixed with the numbered issues. (I.e., numbered issues to not "pop up" wrt. others). --> +<section name="Tomcat 8.0.11 (markt)"> + <subsection name="Coyote"> + <changelog> + <fix> + Fix NIO2 sendfile state tracking and error handling to fix + various corruption issues. (remm) + </fix> + <fix> + Allow inline processing for NIO2 sendfile and optimize keepalive + behavior. (remm) + </fix> + <fix> + Fix excessive NIO2 sendfile direct memory use in some cases, sendfile + will now instead use the regular socket write buffer as configured. + (remm) + </fix> + </changelog> + </subsection> +</section> <section name="Tomcat 8.0.10 (markt)"> <subsection name="Catalina"> <changelog> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org