09 maja 2012

Uruchomienie wątku w servlecie w Servlet 3.0

Wczorajszy wpis "Asynchroniczne przetwarzanie w Servlet 3.0" przyciągnął uwagę Tomka oraz Grześka (a wiem również, że i Marcina) i utwierdził mnie w przekonaniu, że jeśli tylko temat poruszy się we właściwy sposób, może przyciągnąć uwagę skutecznie. Chciałbym móc poświęcić więcej czasu na Scalę i na niej budować takie zagadki, ale nie tym razem. Może ktoś zainspirowany spróbuje pociągnąć temat na swoim blogu, albo chciałby skorzystać z mojego?

Kontynuując poznawanie Servlet 3.0, mam kolejną zagadkę.

Poniższy servlet działa poprawnie, ale jedynie w obecnej "gołej" konfiguracji - nie korzystam chociażby z żadnych mechanizmów bezpieczeństwa. Gdzie jest potencjalny problem? Proszę wskazać linię jego możliwego wystąpienia. Dla pewnego uproszczenia zagadki podaję namiary na javax.servlet.AsyncContext.

package pl.japila;

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

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
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 {

        // Put the request into asynchronous mode
        request.startAsync();

        // Run an asynchronous task
        MyTask mt = new MyTask(request, response);
        mt.run();
    }

    public class MyTask implements Runnable {

        HttpServletRequest request;
        HttpServletResponse response;

        public MyTask(HttpServletRequest request, HttpServletResponse response) {
            super();
            this.request = request;
            this.response = response;
        }

        @Override
        public void run() {
            AsyncContext asyncCtx = request.getAsyncContext();
            try {
                // do the job
                PrintWriter out = response.getWriter();
                out.printf("<h2>Hello from %s</h2>", this);
                out.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                asyncCtx.complete();
            }
        }
    }
}

10 komentarzy:

  1. Za moich czasów aby wystartować wątek konieczne było utworzenie instancji klasy Thread i wywołanie start(). Ty wołasz Runnable.run(). Czyli linia 25 jest nie w porządku.

    Co prawda kod działa, ale metoda doGet() czeka na zakończenie MyTask, a zatem w ogóle nie korzystasz z potencjału asynchronicznego przetwarzania. Dalej wątek HTTP się blokuje.

    Gdybym miał czepiać się dalej: tworzenie wątku per żądanie nie jest najlepszym pomysłem, ale o tym na pewno wiesz.

    OdpowiedzUsuń
  2. Linia 25 wyłapana. Gratulacje (aczkolwiek tutaj upatrywałbym mojego błędu, a nie czegoś zamierzonego! :))

    A gdyby zamienić run() na start() (i pomijając kwestię uruchamiania wątku per żądanie), czy znalazłbyś jeszcze jakiś problem, równie ważny niż te wskazane?

    OdpowiedzUsuń
  3. Zmuszony jestem się poddać, oprócz wątpliwych problemów jak (a) korzystanie z oryginalnego HttpServletResponse w linii 44 zamiast przekazania AsyncContext jako jedynego argumentu do MyTask w linii (i użycia AsyncContext.getResponse()) oraz (b) brak obsługi timeoutów - nic nie widzę.

    A że linia 25 to przypadkowy błąd się domyśliłem, ale to był mój łatwy do zebrania owoc :-).

    OdpowiedzUsuń
  4. Znów odeślę do mojej odpowiedzi na SO: What's the purpose of AsyncContext.start(…) in Servlet 3.0?. W skrócie: nie warto, w żadnym wypadku. Prawdę mówiąc nie wiem co twórca API chciał osiągnąć tą metodą. Co prawda nie blokujemy już wątków HTTP, ale nasze asynchroniczne żądania i tak utkną na innej puli wątków.

    Innymi słowy jesteśmy w stanie "odebrać" znacznie więcej równoległych połączeń, ale nadal nie możemy ich obsłużyć równolegle, będąc ograniczonymi tą wewnętrzną pulą. To już wolę odrzucać nadmiarowe połączenia, skoro nie dam rady ich obsłużyć. Cały sens asynchronicznych servletów to sytuacje, gdzie wiele połączeń można obsłużyć małą liczbą wątków. Przykładowo mam 10 tysięcy przeglądarek połączonych z moim serwerem (AJAX/Comet), którym wysyłam raz na kilka sekund aktualizację danych (broadcast) - jednym malutkim wątkiem na serwerze.

    BTW - to gdzie jest haczyk w kodzie powyżej? :-)

    OdpowiedzUsuń
  5. A ja twierdzę (właśnie sprawdzam), że użycie start() to właśnie skorzystanie z puli wątków Executor, które są zarządzane przez serwer aplikacyjny. Badam zachowanie WebSphere Application Server V8 i będę raportował o postępach. Oczekuję samych pozytywów, bo inaczej...rzucam robotę! :)

    Haczyk był jedynie w *braku* użycia AC.start().

    OdpowiedzUsuń
  6. W tym kontekście chyba najlepszą odpowiedzią jest ta, którą Simon umieścił na blogu Tomka: http://nurkiewicz.blogspot.com/2012/05/javaxservletservletrequeststartasync.html?showComment=1337119893832#c1729708348617526118

    Jest mnóstwo narzędzi (nawet w bibliotece standardowej) z różnego rodzaju ciekawymi pulami, metodami koordynacji itd. Są biblioteki do asynchronicznego IO i generalnie do asynchronicznej obsługi wywołań. Przy każdej z tych opcji dobrze wiemy, co jest grane i jak to kontrolować. Nie ma tak dobrze, żeby jedno rozwiązanie pasowało do wszystkich problemów, zwłaszcza z tak ogólnym interfejsem.

    OdpowiedzUsuń
    Odpowiedzi
    1. Cześć Konrad,

      Dopiero teraz zebrałem się w sobie i przeczytałem wszystkie odpowiedzi do wpisu Tomka. Ciekawa lektura. Pamiętam jednak moment, kiedy zobaczyłem to pierwszy raz - zmroziła mnie ilość tekstu. Teraz wiem, że każde słowo miało swoje znaczenie i pewnie nie dałoby się tego skrócić bez straty wartości przekazu.

      Dzięki Tobie, żeby nie napisać przez Ciebie, ponownie mam wątpliwość, które podejście jest właściwe - osobna pula czy pojedyncza. Suma sumarum i tak jest to ograniczone przez liczbę dostępnych wątków na JVM. Trudno mi znaleźć powód, dla którego zajęcie wszystkich wątków w jednej puli i posiadanie ich w innej ma być lepsze, kiedy i tak potrzebujemy tych wątków. Zagmatwane to :)

      Dzięki za zwrócenie mi uwagi na komentarze u Tomka i zachęcenie do ich lektury. Nie żałuję! Mówią, że aby stać się światłym, należy się takimi otaczać. Właśnie tego doświadczam! :P

      Jacek

      Usuń
    2. A propos - piszemy summa summarum [wym. suma sumarum] «wszystko razem wziąwszy, podsumowując»

      Usuń
    3. Dzięki coola. Takich komentarzy potrzebuję zdecydowanie więcej, aby okiełznać trudną sztukę pisania poprawnie po polsku.

      Usuń