Blob Blame History Raw
--- java/org/apache/tomcat/util/net/NioEndpoint.java.orig	2017-03-28 14:39:31.522852483 -0400
+++ java/org/apache/tomcat/util/net/NioEndpoint.java	2017-03-28 14:39:31.528852515 -0400
@@ -1406,11 +1406,15 @@
                 }
             }catch ( IOException x ) {
                 if ( log.isDebugEnabled() ) log.debug("Unable to complete sendfile request:", x);
-                cancelledKey(sk,SocketStatus.ERROR,false);
+                if (!calledByProcessor) {
+                    cancelledKey(sk,SocketStatus.ERROR,false);
+                }
                 return SendfileState.ERROR;
             }catch ( Throwable t ) {
                 log.error("",t);
-                cancelledKey(sk, SocketStatus.ERROR, false);
+                if (!calledByProcessor) {
+                    cancelledKey(sk, SocketStatus.ERROR, false);
+                }
                 return SendfileState.ERROR;
             }
         }
--- webapps/docs/changelog.xml.orig	2017-03-28 14:39:31.523852488 -0400
+++ webapps/docs/changelog.xml	2017-03-28 14:41:38.105546243 -0400
@@ -57,6 +57,16 @@
   They eventually become mixed with the numbered issues. (I.e., numbered
   issues do not "pop up" wrt. others).
 -->
+<section name="Tomcat 7.0.69-11 (csutherl)">
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        <bug>60409</bug>: When unable to complete sendfile request, ensure the
+        Processor will be added to the cache only once. (markt/violetagg)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
 <section name="Tomcat 7.0.69-9 (csutherl)">
   <subsection name="Catalina">
     <changelog>
--- test/org/apache/catalina/connector/TestSendFile.java.orig	2017-03-28 14:39:31.524852493 -0400
+++ test/org/apache/catalina/connector/TestSendFile.java	2017-03-28 14:39:31.527852510 -0400
@@ -22,10 +22,14 @@
 import java.io.FileInputStream;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -33,6 +37,8 @@
 import javax.servlet.http.HttpServletResponse;
 
 import static org.junit.Assert.assertEquals;
+
+import org.junit.Assert;
 import org.junit.Test;
 
 import org.apache.catalina.Context;
@@ -163,4 +169,97 @@
         }
     }
 
+
+    @Test
+    public void testBug60409() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        Context ctx = tomcat.addContext("", TEMP_DIR);
+        File file = generateFile(TEMP_DIR, "", EXPECTED_CONTENT_LENGTH);
+        Tomcat.addServlet(ctx, "test", new Bug60409Servlet(file));
+        ctx.addServletMapping("/", "test");
+
+        tomcat.start();
+
+        ByteChunk bc = new ByteChunk();
+        getUrl("http://localhost:" + getPort() + "/test/?" + Globals.SENDFILE_SUPPORTED_ATTR
+                + "=true", bc, null);
+
+        CountDownLatch latch = new CountDownLatch(2);
+        List<Throwable> exceptions = new ArrayList<Throwable>();
+        new Thread(
+                new RequestExecutor("http://localhost:" + getPort() + "/test/", latch, exceptions))
+                        .start();
+        new Thread(
+                new RequestExecutor("http://localhost:" + getPort() + "/test/", latch, exceptions))
+                        .start();
+
+        latch.await(3000, TimeUnit.MILLISECONDS);
+
+        if (exceptions.size() > 0) {
+            Assert.fail();
+        }
+    }
+
+    private static final class Bug60409Servlet extends HttpServlet {
+        private static final long serialVersionUID = 1L;
+        private final File file;
+
+        Bug60409Servlet(File file) {
+            this.file = file;
+        }
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            if (Boolean.valueOf(req.getParameter(Globals.SENDFILE_SUPPORTED_ATTR)).booleanValue()) {
+                resp.setContentType("'application/octet-stream");
+                resp.setCharacterEncoding("ISO-8859-1");
+                resp.setContentLength((int) file.length());
+                req.setAttribute(Globals.SENDFILE_FILENAME_ATTR, file.getAbsolutePath());
+                req.setAttribute(Globals.SENDFILE_FILE_START_ATTR, new Long(0));
+                req.setAttribute(Globals.SENDFILE_FILE_END_ATTR, new Long(file.length()));
+                file.delete();
+            } else {
+                byte[] c = new byte[1024];
+                Random rd = new Random();
+                rd.nextBytes(c);
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                resp.getOutputStream().write(c);
+            }
+        }
+
+    }
+
+    private static final class RequestExecutor implements Runnable {
+        private final String url;
+        private final CountDownLatch latch;
+        private final List<Throwable> exceptions;
+
+        RequestExecutor(String url, CountDownLatch latch, List<Throwable> exceptions) {
+            this.url = url;
+            this.latch = latch;
+            this.exceptions = exceptions;
+        }
+
+        @Override
+        public void run() {
+            try {
+                ByteChunk result = new ByteChunk();
+                int rc = getUrl(url, result, null);
+                Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+                Assert.assertEquals(1024, result.getLength());
+            } catch (Throwable e) {
+                e.printStackTrace();
+                exceptions.add(e);
+            } finally {
+                latch.countDown();
+            }
+        }
+
+    }
 }