Hi everyone,

I'm trying to use the new async servlet mechanism introduced in 3.0 to serve a large file over a long period of time without occupying a thread. There is example code for doing this from the Jetty site. I have attached a slightly modified and simplified version here:

@WebServlet(asyncSupported = true, name = "PrinceOfDarkness", urlPatterns = "/prince")
public final class DataRateLimitedServlet extends HttpServlet {

    private int buffersize = 8192;
    private long pauseNS = TimeUnit.MILLISECONDS.toNanos(100);
    ScheduledThreadPoolExecutor scheduler;

    @Override
    public void init() throws ServletException {
        // Create and start a shared scheduler.
        scheduler = new ScheduledThreadPoolExecutor(pool);
    }

    @Override
    public void destroy() {
        scheduler.shutdown();
    }

    @Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/x-data");

// If we have a file path and this is a jetty response, we can use the JettyStream impl
        ServletOutputStream out = response.getOutputStream();

        // Jetty API was not used, so lets try the standards approach
        // Can we find the content as an input stream
InputStream content = new FileInputStream("/home/me/PRINCE_OF_DARKNESS.iso");
        if (content == null) {
            response.sendError(404);
            return;
        }

// Set a StandardStream as he write listener to write the content asynchronously out.setWriteListener(new StandardDataStream(content, request.startAsync(), out));
    }

    /**
     * A standard API Stream writer
     */
private final class StandardDataStream implements WriteListener, Runnable {

        private final InputStream content;
        private final AsyncContext async;
        private final ServletOutputStream out;

private StandardDataStream(InputStream content, AsyncContext async, ServletOutputStream out) {
            this.content = content;
            this.async = async;
            this.out = out;
        }

        @Override
        public void onWritePossible() throws IOException {
            // If we are able to write
            if (out.isReady()) {
// Allocated a copy buffer for each write, so as to not hold while paused
                // TODO put these buffers into a pool
                byte[] buffer = new byte[buffersize];

                // read some content into the copy buffer
                int len = content.read(buffer);

                // If we are at EOF
                if (len < 0) {
                    // complete the async lifecycle
                    async.complete();
                    return;
                }

// write out the copy buffer. This will be an asynchronous write // and will always return immediately without blocking. If a subsequent // call to out.isReady() returns false, then this onWritePossible method
                // will be called back when a write is possible.
                out.write(buffer, 0, len);

// Schedule a timer callback to pause writing. Because isReady() is not called,
                // a onWritePossible callback is no scheduled.
                scheduler.schedule(this, pauseNS, TimeUnit.NANOSECONDS);
            }
        }

        @Override
        public void run() {
            try {
// When the pause timer wakes up, call onWritePossible. Either isReady() will return // true and another chunk of content will be written, or it will return false and the // onWritePossible() callback will be scheduled when a write is next possible.
                onWritePossible();
            } catch (Exception e) {
                onError(e);
            }
        }

        @Override
        public void onError(Throwable t) {
            getServletContext().log("Async Error", t);
            async.complete();
        }
    }

}

I'm using this to serve a big file. But it doesn't get to serve a big file. It gives this exception (Tomcat 8.0.32):

ApplicationContext.log Async Error
 java.lang.IllegalStateException: not in non blocking mode.
    at org.apache.coyote.Response.isReady(Response.java:609)
at org.apache.catalina.connector.OutputBuffer.isReady(OutputBuffer.java:663)

What is weird is that it gets this after serving exactly 2457600 (300 * 8192) bytes, or at 2531328 (309 * 8192) bytes of the file.

Either I'm doing something wrong, or there's a major bug in the way Tomcat is handling async output.

Btw Jetty is showing exactly the same behavior, failing at 300*8192 bytes.

Any ideas? AsyncContext seems like a great idea but is it used in the real world? If so, how?

Thanks

Reply via email to