Miało nie być trzech postów o supervisor, ale idealnie mi się wszystko składa w kupę i ten post był wymagany do tego by za tydzień nie rozbijać tematu na dwa posty. Przez ostatnie dwa tygodnie nauczyliśmy się tworzyć nadzorców z poziomu modułu jak i z iex
. Dziś zaczynamy drogę do końca nauki podstaw elixira. Od dzisiaj będziemy pracować z narzędziem mix
o którym wspomniałem już na samym początku. Naszym celem będzie zaprzyjaźnienie się z tym narzędziem, zobaczenie co ono oferuje, i powoli wejście w tworzenie aplikacji.
W tym tygodniu wykorzystamy naszą wiedzę o supervisor i stworzymy aplikację wykorzystującą dwa sposoby tworzenia nadzorców. Ogólnie, celem jest stworzenie drzewka, które będzie nam potrzebne do artykułu w przyszłym tygodniu. Dodatkowo będziemy musieli zmodyfikować nasz PingPong.Server
by działał on trochę bardziej dynamiczniej – tak byśmy mogli mieć jego dwie instancje działające na raz. Aktualnie to nie jest możliwe ze względu na przypisanie GenSever
to atom :pingpong
.
Modyfikacja PingPong.Server
Naszym pierwszym krokiem będzie tak stworzenie PingPong.Server
byśmy mogli posiadać dwie instancje tego serwera, które nie będą się gryzły między sobą.
W tym celu zróbmy najprostszą z możliwych rzeczy. Zaś inne opcje jakie możemy zrobić opiszę kiedy indziej. Musimy zmodyfikować dwie metody, start_link/0
oraz ping/0
tak by przyjmowały jeden dodatkowy parametr – name
:
defmodule PingPong.Server do use GenServer def start_link(name) do GenServer.start_link(__MODULE__, 0, name: name) end def handle_call({:ping, msg}, from, state) do IO.puts "##{state} Received: #{msg} from #{inspect(from)}" {:reply, {:pong, "...pong ##{state}"}, state + 1} end def ping(name) do GenServer.call(name, {:ping, "ping"}, 5000) end end
Nowy projekt, nowa aplikacja
Mając gotowy PingPong.Server
, możemy przystąpić do działania. Stwórzmy sobie aplikację z wykorzystaniem mix
. Nazwijmy ją sup_app
. Normalnie komendą jaką byśmy wykorzystali do jej stworznia byłaby komenda:
mix new sup_app
Jednak, my wiemy co chcemy. Chcemy stworzyć drzewko nadzorcy. Jak się okazuje, mix
nam to trochę ułatwia i jak przekażemy parametr --sup
to stworzy on nam odpowiednią klasę i wstępną implementację odpalania supervisor z wykorzystaniem kodu znanego nam z poprzedniego rozdziału.
mix new sup_app --sup
Te dwie komendy różnią zawartościcą pliku sup_app.ex
w katalogu /lib
. W pierwszej opcji, jest to pusty plik, w drugiej wygląda on tak:
defmodule SupApp do use Application # See http://elixir-lang.org/docs/stable/elixir/Application.html # for more information on OTP Applications def start(_type, _args) do import Supervisor.Spec, warn: false # Define workers and child supervisors to be supervised children = [ # Starts a worker by calling: SupApp.Worker.start_link(arg1, arg2, arg3) # worker(SupApp.Worker, [arg1, arg2, arg3]), ] # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html # for other strategies and supported options opts = [strategy: :one_for_one, name: SubTest2.Supervisor] Supervisor.start_link(children, opts) end end
Kod chyba jest zrozumiały? Tutaj wykorzystujemy use Application
. O tym jeszcze napiszę. Ale pewnie dopiero za dwa/trzy tygodnie. Na razie jedyne co musimy wiedzieć to, to, że Application
jest odpowiedzialna za wystartowanie i zatrzymanie jakieś określonej funkcjonalności, która może być re-wykorzystywana w innych systemach. Blah blah blah… aplikacja to aplikacja (tak to specjalnie zostało napisane).
Niezależnie od Application
, powinniście zauważyć kod odpowiedzialny za tworzenie nadzorcy, który wygląda prawie jak 1-1 z tym co żeśmy napisali w zeszłym tygodniu. To co my teraz zrobimy, to go lekko doszlifujemy.
Stworzenie pierwszego PingPong.Server
Mając gotową aplikację, stwórzmy nasz pierwszy PingPong.Server
. A następnie upewnijmy się w iex, że to działa. W tym celu, do /lib
dodajmy zmodyfikowany plik PingPongServer.ex
. Następnie musimy dodać nasz serwer jako worker. Robimy to poprzez dodanie wpisy do children
:
worker(PingPong.Server, [:fromSupApp])
To co tutaj się dzieje:
- Dodajemy nowego pracownika
- Przekazujemy parametry do naszego serwera. Każdy osoby element to, nowy parametr w naszym
start_link
. My mamy jeden name, więc tylko w liście przekazujemy jeden element - Pomijamy resztę opcji jako, że defaultowo będą one dobre
Mając stworzonego worker, możemy przetestować nasze rozwiązanie:
iex -S mix
A następnie:
iex> Process.whereis :fromSupApp #PID<0.120.0> iex> PingPong.Server.ping :fromSupApp #0 Received: ping from {#PID<0.121.0>, #Reference<0.0.4.369>} {:pong, "...pong #0"}
Jak to nam działa, to możemy wykonać std kod już nasz do ubijana procesu i sprawdzania czy aby na pewno wstał:
Process.whereis(:fromSupApp) Process.whereis(:fromSupApp) |> Process.exit(:kill) Process.whereis(:fromSupApp) Process.whereis(:fromSupApp) |> Process.exit(:kill) Process.whereis(:fromSupApp)
Jak nam działa to super. Pozostaje nam teraz dodanie supervisor jako dziecka.
Dodanie supervisor
Weźmy nasz moduł nadzorcy z pierwszego posta, dodajmy go do /lib
i lekko go zmodyfikujmy pod nowe parametry PingPong.Server
. Czyli z:
defmodule PingPong.Supervisor do use Supervisor # http://erlang.org/doc/man/supervisor.html#start_link-2 def start_link do Supervisor.start_link(__MODULE__, []) end # http://erlang.org/doc/man/supervisor.html#Module:init-1 def init(args) do children = [ worker(PingPong.Server, []) ] supervise(children, strategy: :one_for_one) end end
Stwórzmy to:
defmodule PingPong.Supervisor do use Supervisor # http://erlang.org/doc/man/supervisor.html#start_link-2 def start_link do Supervisor.start_link(__MODULE__, []) end # http://erlang.org/doc/man/supervisor.html#Module:init-1 def init(args) do children = [ worker(PingPong.Server, [:fromPingPongSup]) ] supervise(children, strategy: :one_for_one) end end
Jedyną różnicą jest definicja worker
– podajemy nazwę i to nazwę różniącą się od :fromSupApp
.
Dlaczego dodaliśmy ten moduł i go modyfikowaliśmy? Po to by teraz dodać nadzorcę jako dziecko naszej aplikacji! :) how cool is that? Nadzorca dla nadzorcy! Mając odpowiednio zmodyfikowany plik, możemy przystąpić do zabawy. Nasz kod do rejestrowania dzieci powinien teraz wyglądać następująco:
children = [ worker(PingPong.Server, [:fromSupApp]), supervisor(PingPong.Supervisor, []) ]
Tym razem wykorzystaliśmy supervisor/3
– aż chce się sprawdzić jak to się zachowa i czy to zadziała. Nie ma co czekać :)
iex -S mix
I zróbmy dwa proste testy:
iex> PingPong.Server.ping :fromPingPongSup #0 Received: ping from {#PID<0.123.0>, #Reference<0.0.8.745>} {:pong, "...pong #0"} iex> PingPong.Server.ping :fromSupApp #0 Received: ping from {#PID<0.123.0>, #Reference<0.0.8.759>} {:pong, "...pong #0"} iex> PingPong.Server.ping :fromSupApp #1 Received: ping from {#PID<0.123.0>, #Reference<0.0.8.764>} {:pong, "...pong #1"} iex> PingPong.Server.ping :fromPingPongSup #1 Received: ping from {#PID<0.123.0>, #Reference<0.0.8.769>} {:pong, "...pong #1"}
Zwróćmy uwagę na dwie rzeczy:
- Że wykonujemy ten kod z tego samego proces #PID<0.123.0>
- Że wysyłamy go do różnych serwerów i każdy zwraca swój własny count
To co teraz możemy zrobić to ubić jeden z procesów i zobaczyć co się stanie:
Process.whereis(:fromPingPongSup) |> Process.exit(:kill) Process.whereis(:fromPingPongSup)
Jeżeli wykonamy kolejny ping to zobaczymy, że count został zresetowany:
iex> PingPong.Server.ping :fromPingPongSup #0 Received: ping from {#PID<0.123.0>, #Reference<0.0.8.780>} {:pong, "...pong #0"}
Ale nie dla :fromSupApp
iex> PingPong.Server.ping :fromSupApp #2 Received: ping from {#PID<0.123.0>, #Reference<0.0.8.785>} {:pong, "...pong #2"}
Co dalej
Teraz, można się pobawić, pododawać kolejne serwery PingPing, zobaczyć różne strategie, różne wartości restartowania. Dla przykładu, raz ubity proces dla serwera stworzonego przez PingPongSupervisor
nigdy nie powinien się podnieść:
worker(PingPong.Server, [:fromPingPongSup], restart: :temporary)
I test:
iex> PingPong.Server.ping :fromPingPongSup #0 Received: ping from {#PID<0.123.0>, #Reference<0.0.6.906>} {:pong, "...pong #0"} iex> Process.whereis(:fromPingPongSup) |> Process.exit(:kill) true iex> PingPong.Server.ping :fromPingPongSup ** (exit) exited in: GenServer.call(:fromPingPongSup, {:ping, "ping"}, 5000) ** (EXIT) no process (elixir) lib/gen_server.ex:596: GenServer.call/3
Podsumowanie
Nie było tutaj nic trudnego ani nic nowego. Wszystko co dzisiaj przeczytaliście powinniście już wiedzieć z poprzednich postów. To co jednak dowiedzieliśmy się, to, to, że możemy stworzyć aplikację za pomocą mix
od razu ze wsparciem Supervisor
. Zaś kod tego supervisor wygląda tak samo jak kod który wykorzystywaliśmy w poprzednim tygodniu przy pisaniu skryptu.
Dodatkowo mogliśmy się pobawić jak wpływają różne strategie i różne sposoby restartowania procesów na nasz kod. Także stworzyliśmy aplikację, która posłuży nam jako model do następnych dwóch jak nie więcej postów.
A teraz bądźcie ze mną szczerzy, czy GenServer
i Supervisor
jest przez was zrozumiały? Czy czegoś wam tutaj brakuje? Bo to są building blocksy elixir i erlanga. Bez zrozumienia tego będzie ciężko :( Dajcie znać w komentarzach, dzięki!
“Dla przykładu, raz upity proces dla serwera stworzonego przez PingPongSupervisor nigdy nie powinien się podnieść […]” – zależy co pił :D
dzięki, poprawione
[…] 21 odcinka stworzyć kilka instancji PingPong.Server w elixir. Wtedy też wykorzystaliśmy jeden sposób, […]
Comments are closed.