This is an automated email from the ASF dual-hosted git repository. markt-asf pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 3218b71b49f4e9cdb4af3c932f6c7bc60e492ccf Author: Mark Thomas <[email protected]> AuthorDate: Wed Nov 11 13:31:59 2026 +0000 Fix BZ 70287. Restore the keep-alive timeout after a sendfile write. Test case written by Claude / Sonnet 5.5 Patch provided by Artur Shellunts. --- java/org/apache/coyote/http11/Http11Processor.java | 3 + .../apache/catalina/connector/TestSendFile.java | 64 ++++++++++++++++++++++ webapps/docs/changelog.xml | 8 +++ 3 files changed, 75 insertions(+) diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/java/org/apache/coyote/http11/Http11Processor.java index 53a4cc870e..1540a24b23 100644 --- a/java/org/apache/coyote/http11/Http11Processor.java +++ b/java/org/apache/coyote/http11/Http11Processor.java @@ -1497,6 +1497,9 @@ public class Http11Processor extends AbstractProcessor { if (sendfileData != null && !getErrorState().isError()) { if (keepAlive) { if (available(false) == 0) { + // If sendfile does not complete inline, the endpoint will register the socket for read once it + // does, without returning to this processor. Set the keep-alive timeout for the next request now. + socketWrapper.setReadTimeout(protocol.getKeepAliveTimeout()); sendfileData.keepAliveState = SendfileKeepAliveState.OPEN; } else { sendfileData.keepAliveState = SendfileKeepAliveState.PIPELINED; diff --git a/test/org/apache/catalina/connector/TestSendFile.java b/test/org/apache/catalina/connector/TestSendFile.java index 82ae639c68..9ea133dcf1 100644 --- a/test/org/apache/catalina/connector/TestSendFile.java +++ b/test/org/apache/catalina/connector/TestSendFile.java @@ -22,6 +22,10 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -240,4 +244,64 @@ public class TestSendFile extends TomcatBaseTest { } } + + /* + * If sendfile does not complete inline, the idle connection must wait for the next request using + * keepAliveTimeout, not connectionTimeout. + * + * https://bz.apache.org/bugzilla/show_bug.cgi?id=70287 + */ + @Test + public void testKeepAliveTimeoutAfterPendingSendfile() throws Exception { + Tomcat tomcat = getTomcatInstance(); + tomcat.getConnector().setProperty("connectionTimeout", "3000"); + tomcat.getConnector().setProperty("keepAliveTimeout", "20000"); + + Context ctx = tomcat.addContext("", TEMP_DIR); + // Large enough that sendfile can't complete while the client isn't reading + File file = generateFile(TEMP_DIR, "-keepalive", 64 * 1024 * 1024); + addDeleteOnTearDown(file); + Tomcat.addServlet(ctx, "test", new WritingServlet(file)); + ctx.addServletMapping("/", "test"); + + tomcat.start(); + + try (Socket s = new Socket("localhost", getPort())) { + long requestTime = System.nanoTime(); + s.getOutputStream().write(("GET /test HTTP/1.1\r\nHost: localhost\r\n\r\n").getBytes( + StandardCharsets.ISO_8859_1)); + s.getOutputStream().flush(); + + // Let the server block on a pending sendfile, then drain the response + Thread.sleep(1000); + InputStream is = s.getInputStream(); + s.setSoTimeout(10000); + byte[] buf = new byte[65536]; + long total = 0; + long expected = file.length(); + StringBuilder hdr = new StringBuilder(); + // Read headers + int b; + while (!hdr.toString().endsWith("\r\n\r\n") && (b = is.read()) != -1) { + hdr.append((char) b); + } + while (total < expected) { + int n = is.read(buf); + Assert.assertTrue("Premature EOF after [" + total + "] bytes", n > 0); + total += n; + } + + // Response complete. Connection is idle. connectionTimeout (3s) has been exceeded (measured from the + // request) after 4s but keepAliveTimeout (20s) has not. The connection must still be open. + long remaining = 5000 - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - requestTime); + Thread.sleep(Math.max(remaining, 0)); + s.setSoTimeout(500); + try { + int r = is.read(); + Assert.fail("Unexpected data/close: server closed idle connection early (read returned " + r + ")"); + } catch (SocketTimeoutException expectedTimeout) { + // Connection still open - expected + } + } + } } diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 30d6b8bd92..298834bfc2 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -108,6 +108,14 @@ issues do not "pop up" wrt. others). --> <section name="Tomcat 10.1.63 (schultz)" rtext="in development"> + <subsection name="Coyote"> + <changelog> + <fix> + <bug>70287</bug>: Restore the keep-alive timeout after a sendfile write. + Patch provided by Artur Shellunts. (markt) + </fix> + </changelog> + </subsection> <subsection name="Jasper"> <changelog> <fix> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
