This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 8.5.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/8.5.x by this push: new bd6cf00963 Fix BZ 66076. Non-blocking flush with TLS+NIO must flush network buffer bd6cf00963 is described below commit bd6cf009630f3ff49056d246413765c2c05cc372 Author: Mark Thomas <ma...@apache.org> AuthorDate: Mon May 23 10:17:02 2022 +0100 Fix BZ 66076. Non-blocking flush with TLS+NIO must flush network buffer https://bz.apache.org/bugzilla/show_bug.cgi?id=66076 --- java/org/apache/tomcat/util/net/NioEndpoint.java | 43 ++++++++++++++++------ .../apache/tomcat/util/net/SocketWrapperBase.java | 23 +++++++++++- webapps/docs/changelog.xml | 5 +++ 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java index ab9b2552f5..a6eaac0b3a 100644 --- a/java/org/apache/tomcat/util/net/NioEndpoint.java +++ b/java/org/apache/tomcat/util/net/NioEndpoint.java @@ -1245,20 +1245,20 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> @Override protected boolean flushNonBlocking() throws IOException { - boolean dataLeft = !socketBufferHandler.isWriteBufferEmpty(); + boolean dataLeft = socketOrNetworkBufferHasDataLeft(); // Write to the socket, if there is anything to write if (dataLeft) { doWrite(false); - dataLeft = !socketBufferHandler.isWriteBufferEmpty(); + dataLeft = socketOrNetworkBufferHasDataLeft(); } if (!dataLeft && !nonBlockingWriteBuffer.isEmpty()) { dataLeft = nonBlockingWriteBuffer.write(this, false); - if (!dataLeft && !socketBufferHandler.isWriteBufferEmpty()) { + if (!dataLeft && socketOrNetworkBufferHasDataLeft()) { doWrite(false); - dataLeft = !socketBufferHandler.isWriteBufferEmpty(); + dataLeft = socketOrNetworkBufferHasDataLeft(); } } @@ -1266,6 +1266,22 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> } + /* + * https://bz.apache.org/bugzilla/show_bug.cgi?id=66076 + * + * When using TLS an additional buffer is used for the encrypted data + * before it is written to the network. It is possible for this network + * output buffer to contain data while the socket write buffer is empty. + * + * For NIO with non-blocking I/O, this case is handling by ensuring that + * flush only returns false (i.e. no data left to flush) if all buffers + * are empty. + */ + private boolean socketOrNetworkBufferHasDataLeft() { + return !socketBufferHandler.isWriteBufferEmpty() || getSocket().getOutboundRemaining() > 0; + } + + @Override protected void doWrite(boolean block, ByteBuffer buffer) throws IOException { int n = 0; @@ -1510,6 +1526,11 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> return inline; } + @Override + protected boolean hasOutboundRemaining() { + return getSocket().getOutboundRemaining() > 0; + } + @Override public void run() { // Perform the IO operation @@ -1542,13 +1563,13 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> } else { boolean doWrite = true; // Write from main buffer first - if (!socketBufferHandler.isWriteBufferEmpty()) { + if (socketOrNetworkBufferHasDataLeft()) { // There is still data inside the main write buffer, it needs to be written first socketBufferHandler.configureWriteBufferForRead(); do { nBytes = getSocket().write(socketBufferHandler.getWriteBuffer()); - } while (!socketBufferHandler.isWriteBufferEmpty() && nBytes > 0); - if (!socketBufferHandler.isWriteBufferEmpty()) { + } while (socketOrNetworkBufferHasDataLeft() && nBytes > 0); + if (socketOrNetworkBufferHasDataLeft()) { doWrite = false; } // Preserve a negative value since it is an error @@ -1569,7 +1590,8 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> updateLastWrite(); } } - if (nBytes != 0 || !buffersArrayHasRemaining(buffers, offset, length)) { + if (nBytes != 0 || (!buffersArrayHasRemaining(buffers, offset, length) && + !socketOrNetworkBufferHasDataLeft())) { completionDone = false; } } @@ -1577,7 +1599,8 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> setError(e); } } - if (nBytes > 0 || (nBytes == 0 && !buffersArrayHasRemaining(buffers, offset, length))) { + if (nBytes > 0 || (nBytes == 0 && !buffersArrayHasRemaining(buffers, offset, length) && + !socketOrNetworkBufferHasDataLeft())) { // The bytes processed are only updated in the completion handler completion.completed(Long.valueOf(nBytes), this); } else if (nBytes < 0 || getError() != null) { @@ -1596,9 +1619,7 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> } } } - } - } diff --git a/java/org/apache/tomcat/util/net/SocketWrapperBase.java b/java/org/apache/tomcat/util/net/SocketWrapperBase.java index 4a3d489a9a..abfbcef2f2 100644 --- a/java/org/apache/tomcat/util/net/SocketWrapperBase.java +++ b/java/org/apache/tomcat/util/net/SocketWrapperBase.java @@ -722,6 +722,12 @@ public abstract class SocketWrapperBase<E> { } + /** + * Writes all remaining data from the buffers and blocks until the write is + * complete. + * + * @throws IOException If an IO error occurs during the write + */ protected void flushBlocking() throws IOException { doWrite(true); @@ -736,6 +742,14 @@ public abstract class SocketWrapperBase<E> { } + /** + * Writes as much data as possible from any that remains in the buffers. + * + * @return <code>true</code> if data remains to be flushed after this method + * completes, otherwise <code>false</code>. + * + * @throws IOException If an IO error occurs during the write + */ protected abstract boolean flushNonBlocking() throws IOException; @@ -1002,6 +1016,13 @@ public abstract class SocketWrapperBase<E> { */ protected abstract boolean isInline(); + protected boolean hasOutboundRemaining() { + // NIO2 and APR never have remaining outbound data when the + // completion handler is called. NIO needs to override this. + return false; + } + + /** * Process the operation using the connector executor. * @return true if the operation was accepted, false if the executor @@ -1053,7 +1074,7 @@ public abstract class SocketWrapperBase<E> { boolean completion = true; if (state.check != null) { CompletionHandlerCall call = state.check.callHandler(currentState, state.buffers, state.offset, state.length); - if (call == CompletionHandlerCall.CONTINUE) { + if (call == CompletionHandlerCall.CONTINUE || (!state.read && state.hasOutboundRemaining())) { complete = false; } else if (call == CompletionHandlerCall.NONE) { completion = false; diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 13860f4258..7d430b94bb 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -124,6 +124,11 @@ </subsection> <subsection name="Coyote"> <changelog> + <fix> + <bug>66076</bug>: When using TLS with non-blocking writes and the NIO + connector, ensure that flushing the buffers attempts to empty all of the + output buffers. (markt) + </fix> <fix> <bug>66084</bug>: Correctly calculate bytes written to a response. Pull request <pr>516</pr> provided by aooohan HanLi. (markt) --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org