I have a simple servlet which I'm running on Tomcat 8 trunk with Java 7.  It's 
using a non-blocking IO WriteListener to write 8k blocks of static data to the 
response.  The number of blocks it will write is given as a request parameter.

When I start out making individual requests to the server (1-1000 blocks) 
things are fine and those are handled without a problem.  However once I start 
to send multiple requests using JMeter, requests begin to fail.  It starts 
slowly with only a few requests failing and then proceeds to the point where 
every request fails.  At that point, Tomcat will serve JSP pages and Servlets 
that use blocking IO, but it will not serve any requests that use the 
non-blocking IO apis (ReadListener / WriteListener).  If I try to hit the byte 
counter servlet or number writer servlet included in the examples then my 
browser will timeout or display the error "java.lang.IllegalStateException: 
getOutputStream() has already been called for this response".

In my tests most of the time the requests fail because the async context times 
out, but occasionally (one or two times out of 100) I'll see these exceptions.

1.) org.apache.catalina.connector.ClientAbortException: java.io.IOException: 
Key must be cancelled
        at 
org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:396)
        at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:315)
        at 
org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:421)
        at 
org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:409)
        at 
org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:97)
        at 
org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:90)
        at 
com.pivotal.demos.nbio.DataStreamerNbioServlet$2.onWritePossible(DataStreamerNbioServlet.java:64)
        at org.apache.coyote.Response.onWritePossible(Response.java:655)
        at 
org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:369)
        at 
org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1618)
        at 
org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:631)
        at 
org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
        at 
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1576)
        at 
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1534)
        at 
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at 
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)
Caused by: java.io.IOException: Key must be cancelled
        at 
org.apache.coyote.http11.InternalNioOutputBuffer.writeToSocket(InternalNioOutputBuffer.java:131)
        at 
org.apache.coyote.http11.InternalNioOutputBuffer.addToBB(InternalNioOutputBuffer.java:198)
        at 
org.apache.coyote.http11.InternalNioOutputBuffer.access$000(InternalNioOutputBuffer.java:42)
        at 
org.apache.coyote.http11.InternalNioOutputBuffer$SocketOutputBuffer.doWrite(InternalNioOutputBuffer.java:321)
        at 
org.apache.coyote.http11.filters.ChunkedOutputFilter.doWrite(ChunkedOutputFilter.java:116)
        at 
org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:257)
        at org.apache.coyote.Response.doWrite(Response.java:520)
        at 
org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:391)
        ... 16 more

2.) 19-Dec-2013 11:14:34.470 SEVERE [http-nio-8080-ClientPoller-0] 
org.apache.tomcat.util.net.NioEndpoint$PollerEvent.run
 java.nio.channels.ClosedChannelException
        at 
java.nio.channels.spi.AbstractSelectableChannel.register(AbstractSelectableChannel.java:194)
        at 
org.apache.tomcat.util.net.NioEndpoint$PollerEvent.run(NioEndpoint.java:797)
        at 
org.apache.tomcat.util.net.NioEndpoint$Poller.events(NioEndpoint.java:925)
        at 
org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:1045)
        at java.lang.Thread.run(Thread.java:744)

I put together a unit test that replicates the async context timeout issue.  
Code is listed below.  When run, you'll see that it processes some of the 
requests but fails due to a timeout.  I've not been able to replicate the other 
exceptions with the unit test though, so those may be unrelated.

public class TestWriteListener extends TomcatBaseTest {
        private static final int BLOCK_SIZE = 8192;
        private static final byte[] DATA = new byte[BLOCK_SIZE];

    @Test
    public void testEchoListener1() throws Exception {
        Tomcat tomcat = getTomcatInstance();

        // Must have a real docBase - just use temp
        StandardContext ctx = (StandardContext) tomcat.addContext("",
                System.getProperty("java.io.tmpdir"));

        DataStreamingServlet servlet = new DataStreamingServlet();
        String servletName = DataStreamingServlet.class.getName();
        Tomcat.addServlet(ctx, servletName, servlet);
        ctx.addServletMapping("/", servletName);

        tomcat.start();

        Arrays.fill(DATA, Byte.parseByte("1"));

        for (int i = 0; i < 200; i++) {
                int blocks = 200;
                Map<String, List<String>> resHeaders = new HashMap<>();
                ByteChunk out = new ByteChunk();
                int rc = getUrl("http://localhost:"; + getPort() + "/?blocks=" + 
blocks, out, resHeaders);
                Assert.assertEquals(HttpServletResponse.SC_OK, rc);
                Assert.assertEquals(BLOCK_SIZE * blocks, out.getLength());
        }
    }

    @WebServlet(asyncSupported = true)
    public class DataStreamingServlet extends HttpServlet {
        private static final long serialVersionUID = -3038617032944575769L;
        public static final int BUFFER_SIZE = 8 * 1024;

        @Override
        protected void service(HttpServletRequest request, HttpServletResponse 
response) throws ServletException, IOException
        {
                int blocks = Integer.parseInt(request.getParameter("blocks"));

                AsyncContext asyncContext = request.startAsync(request, 
response);
            asyncContext.setTimeout(10);
            asyncContext.addListener(new AsyncListener() {
                                @Override
                                public void onTimeout(AsyncEvent event) throws 
IOException {
                                        Assert.fail("Request has timed out");
                                }

                                @Override
                                public void onStartAsync(AsyncEvent event) 
throws IOException {
                                }

                                @Override
                                public void onError(AsyncEvent event) throws 
IOException {
                                }

                                @Override
                                public void onComplete(AsyncEvent event) throws 
IOException {
                                }
                        });

            DataStreamWriteListener dswl = new 
DataStreamWriteListener(asyncContext, blocks);
            response.getOutputStream().setWriteListener(dswl);
        }

        private class DataStreamWriteListener implements WriteListener {

                private AsyncContext context;
                private int blocks = 1;
                private int block_count = 0;

                public DataStreamWriteListener(AsyncContext context, int 
blocks) {
                        this.context = context;
                        this.blocks = blocks;
                }

                @Override
                        public void onWritePossible() throws IOException {
                        ServletOutputStream output = 
context.getResponse().getOutputStream();

                                while (output.isReady() && block_count < 
blocks) {
                                        output.write(DATA);
                                        block_count += 1;
                                }

                                if (block_count >= blocks) {
                                        context.complete();
                                }
                        }

                        @Override
                        public void onError(Throwable throwable) {
                                throwable.printStackTrace(System.err);
                                context.complete();
                        }
        }
    }
}

Any thoughts on what's happening here?

Thanks

Dan
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org
For additional commands, e-mail: users-h...@tomcat.apache.org

Reply via email to