0

Has anyone tried Servlets 3.1 non blocking technique on tomcat?

The request from the browser seems to be waiting forever but when I run the server in debug mode, the call returns but still I don't see "Data read.." and "Data written.." in the logs.

Servlet:

@WebServlet(urlPatterns = "/asyncn", asyncSupported = true)
public class AsyncN extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {

        println("Before starting job");

        final AsyncContext actx = request.startAsync();
        actx.setTimeout(Long.MAX_VALUE);
        actx.start(new HeavyTask(actx));

        println("After starting job");

    }

    class HeavyTask implements Runnable {
        AsyncContext actx;

        HeavyTask(AsyncContext actx) {
            this.actx = actx;
        }

        @Override
        public void run() {
            try {
                Thread.currentThread().setName("Job-Thread-" + actx.getRequest().getParameter("job"));
                // set up ReadListener to read data for processing
                ServletInputStream input = actx.getRequest().getInputStream();
                ReadListener readListener = new ReadListenerImpl(input, actx);
                input.setReadListener(readListener);
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void println(String output) {
        System.out.println("[" + Thread.currentThread().getName() + "]" + output);
    }
}

Listeners:

public class ReadListenerImpl implements ReadListener {

    private ServletInputStream input = null;
    private AsyncContext actx = null;

    // store the processed data to be sent back to client later
    private Queue<String> queue = new LinkedBlockingQueue<>();

    ReadListenerImpl(ServletInputStream input, AsyncContext actx) {
        this.input = input;
        this.actx = actx;
    }

    @Override
    public void onDataAvailable() throws IOException {
        println("Data is now available, starting to read");
        StringBuilder sb = new StringBuilder();
        int len = -1;
        byte b[] = new byte[8];
        // We need to check input#isReady before reading data.
        // The ReadListener will be invoked again when
        // the input#isReady is changed from false to true
        while (input.isReady() && (len = input.read(b)) != -1) {
            String data = new String(b, 0, len);
            sb.append(data);
        }
        println("Data read: "+sb.toString());
        queue.add(sb.toString());
    }

    @Override
    public void onAllDataRead() throws IOException {
        println("All Data read, now invoking write listener");
        // now all data are read, set up a WriteListener to write
        ServletOutputStream output = actx.getResponse().getOutputStream();
        WriteListener writeListener = new WriteListenerImpl(output, queue, actx);
        output.setWriteListener(writeListener);
    }

    @Override
    public void onError(Throwable throwable) {
        println("onError");
        actx.complete();
        throwable.printStackTrace();
    }

    public static void println(String output) {
        System.out.println("[" + Thread.currentThread().getName() + "]" + output);
    }
}

public class WriteListenerImpl implements WriteListener {

    private ServletOutputStream output = null;
    private Queue<String> queue = null;
    private AsyncContext actx = null;

    WriteListenerImpl(ServletOutputStream output, Queue<String> queue, AsyncContext actx) {
        this.output = output;
        this.queue = queue;
        this.actx = actx;
    }

    @Override
    public void onWritePossible() throws IOException {
        println("Ready to write, writing data");
         // write while there is data and is ready to write
        while (queue.peek() != null && output.isReady()) {
            String data = queue.poll();
            //do some processing here with the data
            try {
                data = data.toUpperCase();
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            println("Data written: "+data);
            output.print(data);
        }
        // complete the async process when there is no more data to write
        if (queue.peek() == null) {
            actx.complete();
        }
    }

    @Override
    public void onError(Throwable throwable) {
        println("onError");
        actx.complete();
        throwable.printStackTrace();
    }

    public static void println(String output) {
        System.out.println("[" + Thread.currentThread().getName() + "]" + output);
    }
}

Sysout logs:

[http-nio-8080-exec-4]Before starting job
[http-nio-8080-exec-4]After starting job

Sysout logs (when I run the server in debug mode):

[http-nio-8080-exec-6]Before starting job
[http-nio-8080-exec-6]After starting job
[http-nio-8080-exec-6]All Data read, now invoking write listener
[http-nio-8080-exec-6]Ready to write, writing data
John Eipe
  • 10,922
  • 24
  • 72
  • 114

1 Answers1

0

Creating the new thread is unnecessary, set the readListener from the service method and everything will work asynchronously.

Couple of comments on your code. In the readListener you have:

while (input.isReady() && (len = input.read(b)) != -1)  

would suggest instead using this to stick fully with the asynchronous api:

while (input.isReady() && !input.isFinished())

Also for your write listener you have:

while (queue.peek() != null && output.isReady())

you should reverse the conditionals to:

while (output.isReady() && queue.peek() != null)

this protects against calling ac.complete() early if the very last write goes asynchronous.

Jason D
  • 8,023
  • 10
  • 33
  • 39
mmulholl
  • 281
  • 2
  • 7
  • So you mean setting the Listeners inside the servlet and not in a separate runnable thread? But would it be handling read and write asynchronously then? – John Eipe Nov 18 '15 at 05:03
  • Correct. If you set the readListener from the service method of the servlet and return you are now doing async i/o. The read listener will be called when data is available, it could be on the same thread as the servlet or a new thread depending on the underlying implementation and when data is available. If for some reason the inbound data gets blocked input.isReady() will return false, the readListener will return and the thread is relinquished. OnDataAvailable() will then be called on a new thread when more data become available. – mmulholl Nov 18 '15 at 15:03
  • Also, in your app registering the writeListener from onAllDataRead(), when all output data is available, is correct. – mmulholl Nov 18 '15 at 15:16
  • I made the changes you mentioned but it's the same. Console output is the same as given above and nothing comes on the page. – John Eipe Nov 19 '15 at 07:33
  • I notice in your output that onDataAvailable is not being called which implies you are not sending a post request with post data. This suggest that there must be some difference in behavior where Tomcat calls onAllDataRead() in debug mode when there is no data but does not in non-debug mode. Send in a post request with post data and you should see it working. – mmulholl Nov 19 '15 at 15:43