--- java/org/apache/coyote/AbstractProtocol.java.orig 2017-08-18 09:12:05.149568367 -0400 +++ java/org/apache/coyote/AbstractProtocol.java 2017-08-18 09:12:55.998699189 -0400 @@ -693,10 +693,10 @@ release(wrapper, processor, false, true); } else if (state == SocketState.SENDFILE) { // Sendfile in progress. If it fails, the socket will be - // closed. If it works, the socket will be re-added to the - // poller - connections.remove(socket); - release(wrapper, processor, false, false); + // closed. If it works, the socket either be added to the + // poller (or equivalent) to await more data or processed + // if there are any pipe-lined requests remaining. + connections.put(socket, processor); } else if (state == SocketState.UPGRADED) { // Need to keep the connection associated with the processor connections.put(socket, processor); --- java/org/apache/coyote/http11/Http11AprProcessor.java.orig 2017-06-08 16:23:31.983000742 -0400 +++ java/org/apache/coyote/http11/Http11AprProcessor.java 2017-06-08 16:23:31.999000805 -0400 @@ -38,6 +38,7 @@ import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; import org.apache.tomcat.util.net.AprEndpoint; import org.apache.tomcat.util.net.SSLSupport; +import org.apache.tomcat.util.net.SendfileKeepAliveState; import org.apache.tomcat.util.net.SocketStatus; import org.apache.tomcat.util.net.SocketWrapper; @@ -211,7 +212,15 @@ // Do sendfile as needed: add socket to sendfile and end if (sendfileData != null && !getErrorState().isError()) { sendfileData.socket = socketWrapper.getSocket().longValue(); - sendfileData.keepAlive = keepAlive; + if (keepAlive) { + if (getInputBuffer().available() == 0) { + sendfileData.keepAliveState = SendfileKeepAliveState.OPEN; + } else { + sendfileData.keepAliveState = SendfileKeepAliveState.PIPELINED; + } + } else { + sendfileData.keepAliveState = SendfileKeepAliveState.NONE; + } switch (((AprEndpoint)endpoint).getSendfile().add(sendfileData)) { case DONE: return false; --- java/org/apache/coyote/http11/Http11NioProcessor.java.orig 2017-06-08 16:23:31.984000746 -0400 +++ java/org/apache/coyote/http11/Http11NioProcessor.java 2017-06-08 16:23:32.000000809 -0400 @@ -37,6 +37,7 @@ import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment; import org.apache.tomcat.util.net.SSLSupport; import org.apache.tomcat.util.net.SecureNioChannel; +import org.apache.tomcat.util.net.SendfileKeepAliveState; import org.apache.tomcat.util.net.SocketStatus; import org.apache.tomcat.util.net.SocketWrapper; @@ -275,7 +276,15 @@ // Do sendfile as needed: add socket to sendfile and end if (sendfileData != null && !getErrorState().isError()) { ((KeyAttachment) socketWrapper).setSendfileData(sendfileData); - sendfileData.keepAlive = keepAlive; + if (keepAlive) { + if (getInputBuffer().available() == 0) { + sendfileData.keepAliveState = SendfileKeepAliveState.OPEN; + } else { + sendfileData.keepAliveState = SendfileKeepAliveState.PIPELINED; + } + } else { + sendfileData.keepAliveState = SendfileKeepAliveState.NONE; + } SelectionKey key = socketWrapper.getSocket().getIOChannel().keyFor( socketWrapper.getSocket().getPoller().getSelector()); //do the first write on this thread, might as well --- java/org/apache/tomcat/util/net/AprEndpoint.java.orig 2017-06-08 16:23:31.985000750 -0400 +++ java/org/apache/tomcat/util/net/AprEndpoint.java 2017-06-08 16:23:32.001000813 -0400 @@ -2106,7 +2106,7 @@ // Position public long pos; // KeepAlive flag - public boolean keepAlive; + public SendfileKeepAliveState keepAliveState = SendfileKeepAliveState.NONE; } @@ -2349,20 +2349,33 @@ state.pos = state.pos + nw; if (state.pos >= state.end) { remove(state); - if (state.keepAlive) { + switch (state.keepAliveState) { + case NONE: { + // Close the socket since this is + // the end of the not keep-alive request. + closeSocket(state.socket); + break; + } + case PIPELINED: { + // Destroy file descriptor pool, which should close the file + Pool.destroy(state.fdpool); + Socket.timeoutSet(state.socket, getSoTimeout() * 1000); + // Process the pipelined request data + if (!processSocket(state.socket, SocketStatus.OPEN_READ)) { + closeSocket(state.socket); + } + break; + } + case OPEN: { // Destroy file descriptor pool, which should close the file Pool.destroy(state.fdpool); - Socket.timeoutSet(state.socket, - getSoTimeout() * 1000); - // If all done put the socket back in the - // poller for processing of further requests - getPoller().add( - state.socket, getKeepAliveTimeout(), + Socket.timeoutSet(state.socket, getSoTimeout() * 1000); + // Put the socket back in the poller for + // processing of further requests + getPoller().add(state.socket, getKeepAliveTimeout(), true, false); - } else { - // Close the socket since this is - // the end of not keep-alive request. - closeSocket(state.socket); + break; + } } } } --- java/org/apache/tomcat/util/net/NioEndpoint.java.orig 2017-06-08 16:23:31.987000757 -0400 +++ java/org/apache/tomcat/util/net/NioEndpoint.java 2017-06-08 16:23:32.002000817 -0400 @@ -1383,16 +1383,30 @@ // responsible for registering the socket for the // appropriate event(s) if sendfile completes. if (!calledByProcessor) { - if ( sd.keepAlive ) { - if (log.isDebugEnabled()) { - log.debug("Connection is keep alive, registering back for OP_READ"); - } - reg(sk,attachment,SelectionKey.OP_READ); - } else { + switch (sd.keepAliveState) { + case NONE: { if (log.isDebugEnabled()) { log.debug("Send file connection is being closed"); } cancelledKey(sk,SocketStatus.STOP,false); + break; + } + case PIPELINED: { + if (log.isDebugEnabled()) { + log.debug("Connection is keep alive, processing pipe-lined data"); + } + if (!processSocket(sc, SocketStatus.OPEN_READ, true)) { + cancelledKey(sk, SocketStatus.DISCONNECT, false); + } + break; + } + case OPEN: { + if (log.isDebugEnabled()) { + log.debug("Connection is keep alive, registering back for OP_READ"); + } + reg(sk, attachment, SelectionKey.OP_READ); + break; + } } } return SendfileState.DONE; @@ -1836,6 +1850,6 @@ public volatile long pos; public volatile long length; // KeepAlive flag - public volatile boolean keepAlive; + public SendfileKeepAliveState keepAliveState = SendfileKeepAliveState.NONE; } } --- webapps/docs/changelog.xml.orig 2017-06-08 16:23:31.989000765 -0400 +++ webapps/docs/changelog.xml 2017-06-08 16:25:23.618440723 -0400 @@ -73,6 +73,13 @@ + + + + Improve sendfile handling when requests are pipelined. (markt) + + +
--- java/org/apache/tomcat/util/net/SendfileKeepAliveState.java.orig 2017-06-08 16:23:31.992000777 -0400 +++ java/org/apache/tomcat/util/net/SendfileKeepAliveState.java 2017-06-08 16:23:32.000000809 -0400 @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.util.net; + +public enum SendfileKeepAliveState { + + /** + * Keep-alive is not in use. The socket can be closed when the response has + * been written. + */ + NONE, + + /** + * Keep-alive is in use and there is pipelined data in the input buffer to + * be read as soon as the current response has been written. + */ + PIPELINED, + + /** + * Keep-alive is in use. The socket should be added to the poller (or + * equivalent) to await more data as soon as the current response has been + * written. + */ + OPEN +}