@Override
    public void doGet(final HttpServletRequest request, final HttpServletResponse response)
        throws ServletException, IOException {
      final AsyncContext async = request.startAsync();
      final AtomicInteger complete = new AtomicInteger(2);
      final AtomicBoolean onDataAvailable = new AtomicBoolean(false);

      // Asynchronous Read
      if (request.getContentLength() > 0) {
        // System.err.println("reading "+request.getContentLength());
        final ServletInputStream in = request.getInputStream();
        in.setReadListener(
            new ReadListener() {
              byte[] _buf = new byte[32];

              @Override
              public void onError(Throwable t) {
                if (complete.decrementAndGet() == 0) async.complete();
              }

              @Override
              public void onDataAvailable() throws IOException {
                if (!onDataAvailable.compareAndSet(false, true)) throw new IllegalStateException();

                // System.err.println("ODA");
                while (in.isReady() && !in.isFinished()) {
                  _oda.incrementAndGet();
                  int len = in.read(_buf);
                  // System.err.println("read "+len);
                  if (len > 0) _read.addAndGet(len);
                }

                if (!onDataAvailable.compareAndSet(true, false)) throw new IllegalStateException();
              }

              @Override
              public void onAllDataRead() throws IOException {
                if (onDataAvailable.get()) {
                  LOG.warn("OADR too early!");
                  _read.set(-1);
                }

                // System.err.println("OADR");
                if (complete.decrementAndGet() == 0) async.complete();
              }
            });
      } else complete.decrementAndGet();

      // Asynchronous Write
      final String[] writes = request.getParameterValues("w");
      final ServletOutputStream out = response.getOutputStream();
      out.setWriteListener(
          new WriteListener() {
            int _w = 0;

            @Override
            public void onWritePossible() throws IOException {
              LOG.debug("OWP");
              _owp.incrementAndGet();

              while (writes != null && _w < writes.length) {
                int write = Integer.valueOf(writes[_w++]);

                if (write == 0) out.flush();
                else {
                  byte[] buf = new byte[write + 1];
                  Arrays.fill(buf, (byte) ('0' + ((_w - 1) % 10)));
                  buf[write] = '\n';
                  out.write(buf);
                }

                if (!out.isReady()) return;
              }

              if (complete.decrementAndGet() == 0) async.complete();
            }

            @Override
            public void onError(Throwable t) {
              async.complete();
            }
          });
    }