This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 01a3ba8042 Fix BZ 70287. Restore the keep-alive timeout after a
sendfile write.
01a3ba8042 is described below
commit 01a3ba804291c1e567179aa34cf8ad0774e721fc
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
---
java/org/apache/coyote/http11/Http11Processor.java | 3 +
.../apache/catalina/connector/TestSendFile.java | 64 ++++++++++++++++++++++
webapps/docs/changelog.xml | 4 ++
3 files changed, 71 insertions(+)
diff --git a/java/org/apache/coyote/http11/Http11Processor.java
b/java/org/apache/coyote/http11/Http11Processor.java
index 2c5195cd2d..3fd7c93608 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -1468,6 +1468,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 e5b20c70f8..012495e1f4 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -251,6 +251,10 @@
Remove support for HTTP 0.9. (markt)
</update>
<!-- Entries for backport and removal before 12.0.0-M1 below this line
-->
+ <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">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]