--- 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). --> +
+ + + + 60409: When unable to complete sendfile request, ensure the + Processor will be added to the cache only once. (markt/violetagg) + + + +
--- 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 exceptions = new ArrayList(); + 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 exceptions; + + RequestExecutor(String url, CountDownLatch latch, List 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(); + } + } + + } }