10 maja 2012

Wątki podczas obsługi asynchronicznego żądania w Servlet 3.0 i IBM WebSphere Application Server 8.0.0.3

Kontynuując moje rozpoznawanie mechanizmów asynchronicznego przetwarzania żądań w Servlet 3.0, tym razem przysiadłem, aby rozpoznać ich obsługę w serwerze aplikacyjnym IBM WebSphere Application Server 8.0.0.3. Stworzyłem servlet, który wyświetla wątki uczestniczące w zadaniu.
package pl.japila;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/AsyncServlet", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

        // Record the servlet's thread
        PrintWriter out = response.getWriter();
        out.printf("<h3>Servlet's thread: %s</h3>", Thread.currentThread());
        out.flush();
        
        // Put the request into asynchronous mode
        request.startAsync();

        // Run an asynchronous task via servlet 3.0's abstractions
        AsyncContext asyncCtx = request.getAsyncContext();
        MyTask mt = new MyTask(asyncCtx);
        asyncCtx.start(mt);
        
        // Run another asynchronous task via java's abstractions
        MyTask mt02 = new MyTask(asyncCtx);
        new Thread(mt02).start();

        out.printf("<h3>Servlet finishes its job</h3>");
    }

    public class MyTask implements Runnable {

        AsyncContext asyncContext;

        public MyTask(AsyncContext asyncContext) {
            this.asyncContext = asyncContext;
        }

        @Override
        public void run() {
            ServletResponse response = asyncContext.getResponse();
            try {
                // do the time-consuming job
                PrintWriter out = response.getWriter();
                for (int i = 0; i < 5; i++) {
                    out.printf("<h2>Hello from thread: %s (%d)</h2>", Thread.currentThread(), i);
                    out.flush();
                    Thread.sleep(1 /* secs */ * 1000);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                asyncContext.complete();
            }
        }
    }
}
Czy widział(a)byś usprawnienia w tym servlecie?

Komentarz Tomka mógłby świadczyć o niepoprawności takiego podejścia, ale w moim przekonaniu jedynym obecnie problemem jest tworzenie wątku każdorazowo po otrzymaniu żądania i opcjonalnie brak określenia czasu oczekiwania na zakończenie. Alternatywnym i sądzę, że możliwym podejściem jest tworzenie pseudo-wątku za pomocą java.util.concurrent.Callable i skorzystanie z mechanizmów oferowanych przez pakiet java.util.concurrent. Na chwilę obecną wiem, że to możliwe (konsultant), ale nie wiem, jak to zrealizować (specjalista).

Wcześniejsze wpisy o przetwarzaniu asynchronicznym w Servlet 3.0:
Jego uruchomienie skutkuje wyświetleniem strony z uczestniczącymi wątkami. Uważam, że podział na dedykowane pule wątków w WASie, które można osobno stroić do potrzeb aplikacji, prezentuje się obiecująco.


A jak to wygląda na Twoim serwerze aplikacyjnym? Ciekawym wyników z GlassFish 3.1.2, JBoss AS 7.1.1, Apache Tomcat 7.0.27, Oracle WebLogic Server 12c i in. Wyniki można słać na priv, albo zamieścić bezpośrednio w komentarzu do tego wpisu. Gorąco zachęcam.

5 komentarzy:

  1. Po kolei, więc musi poczekać na swoją kolej. Zresztą wciąż niedostępny, więc klientom się nie przyda ta wiedza...jeszcze. Planned availability date dla WAS 8.5: June 15, 2012 - Electronic delivery.

    OdpowiedzUsuń
  2. Będę się upierał przy tym, że skorzystanie z innej puli wątków do obsługi asynchronicznego przetwarzania przy zachowaniu modelu "wątek per żądanie" nie rozwiązuje (prawie) żadnego problemu. Aż nabrałem ochotę na wpis pod roboczym tytułem "AsyncContext.start() fundamentally broken". Co do Twojego kodu:

    1. Masz dwa wątki piszące równocześnie do jednego żądania (+ wątek HTTP). To chyba nie jest problem. Problemem jest to, że oba wątki kończą się wywołaniem complete(). A co, jesli jeden wątek wywoła complete() zanim ten drugi skończy pisanie?

    2. Czemu nie zastąpisz pary "request.startAsync()/AsyncContext asyncCtx = request.getAsyncContext()" zwykłym "AsyncContext asyncCtx = request.startAsync()"?

    3. TimeUnit.SECONDS.sleep(1)?

    OdpowiedzUsuń
    Odpowiedzi
    1. Byłbym wdzięczny, gdybyś zechciał podzielić się swoimi opiniami na temat AsyncContext.start(), bo ja mogę zaliczyć się jedynie do początkujących w temacie i jestem w trybie rozpoznania oraz słuchania wokoło.

      Ad 1. I tu mnie masz! Całkowicie o tym zapomniałem. Do poprawy w kolejnej wersji.

      Ad 2. Zależało mi na możliwe obszernym wykorzystaniu API dla celów poglądowych. W kolejnej wersji zniosę to na rzecz bardziej zwartej postaci. Dziękuję!

      Ad 3. A o tym jedynie czytałem, ale nigdy nie miałem okazji zastosować. Teraz będzie okazja. Poprawka do wprowadzenia w kolejnej wersji. Ponownie dziękuję za uwagę.

      Na zakończenie ponawiam moją prośbę o wpis. Byłbym niezmiernie wdzięczny za możliwość wczytania się w Twoje przemyślenia w temacie.

      Usuń
    2. Sprawdziłem jak ta część API działaj pod Tomcatem i Jetty - nie działa, tzn. udaje, że działa :-(. Opisałem też nieco dlaczego AsyncContext.start() jest generalnie kiepskim pomysłem.

      Usuń