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]

Reply via email to