Warning: strpos() [function.strpos]: Empty needle in /home/dealer/kasat/pkg/cahir/web/html/lib/plugins/translation2/action.php on line 53

Warning: Cannot modify header information - headers already sent by (output started at /home/dealer/kasat/pkg/cahir/web/html/lib/plugins/translation2/action.php:53) in /home/dealer/kasat/pkg/cahir/web/html/inc/actions.php on line 163
erlang:otp [Krystian Bacławski Wiki]
 

Wzorce zachowań OTP

Motywacja

Głównym czynnikiem motywującym do stworzenia szablonów jest oddzielenie części specyficznej dla danego rozwiązania od często powtarzającego się wzorca, spełniającego pewne niefunkcjonalne wymagania.

Takimi niefunkcjonalnymi wymaganiami są:

  • tolerowanie awarii,
  • jednolite zarządzanie procesami,
  • zmiana wersji oprogramowania w locie,
  • rejestrowanie nazwy procesu,
  • oddzielenie faktycznej implementacji od zachowania.

Charakterystyczną cechą faktycznej implementacji jest to, że jest to kod całkowicie sekwencyjny, który ukrywa prawdziwą naturę komunikacji między procesami.

Tworzenie własnych modułów zachowań

Nasz moduł z szablonem powinien eksportować następującą funkcję.

behaviour_info(callbacks) ->
  [{Name1,Arity1},...,{NameN,ArityN}].

Przy użyciu zachowania w module implementacji Erlang wywoła nam tą funkcję, żeby pobrać listę wywołań zwrotnych. Następnie sprawdzi czy moduł implementacji eksportuje te funkcje. Jeśli tego nie robi wyświetli ostrzeżenie.

Przykład

Szablon zachowania prostego serwera

-module(simple_server).
-export([start_link/2,...]).
-export([behaviour_info/1]).
 
behaviour_info(callbacks) ->
  [{init,1},
   {handle_req,1},
   {terminate,0}].
 
start_link(Name, Module) ->
  proc_lib:start_link(?MODULE, init, [self(), Name, Module]).
 
init(Parent, Name, Module) ->
  register(Name, self()),
  ...,
  Dbg = sys:debug_options([]),
  proc_lib:init_ack(Parent, {ok, self()}),
  loop(Parent, Module, Deb, ...).

Moduł z wywołaniami zwrotnymi

-module(db).
-behaviour(simple_server).
-export([init/0, handle_req/1, terminate/0]).
 
init() -> ...
handle_req(Request) -> ...
terminate() -> ...

Czym jest proces OTP ?

Proces, który będzie współpracował z procesami OTP, musi spełniać następujące warunki:

  • będzie startował w taki sposób, że da się go umieścić w drzewie procesów zarządzanym przez nadzorcę OTP,
  • będzie wspierał debugowanie przy pomocy modułu sys,
  • będzie obsługiwał komunikaty systemowe.

Implementacja procesów OTP

Proces korzystający z modułu proc_lib będzie zarządzalny zgodnie z konwencjami OTP tzn.:

  • zachowuje informacje nt. procesów będących w otoczeniu (rodzice, nazwy, etc.)
  • kończy działanie bez powiadomienia o błędzie również wtedy, kiedy otrzyma sygnał z Reason == shutdown - jest to używane przez nadzorcę celem zatrzymania procesu w elegancki sposób,
  • jeśli kończy działanie w wyniku błędu, to następuje wygenerowanie raportu błędu i wysyłanie go do aplikacji SASL, która domyślnie wydrukuje ten komunikat na ekran.

Startowanie

Asynchroniczne

proc_lib:spawn(Module, Function, Args) -> pid()
proc_lib:spawn(Node, Module, Function, Args) -> pid()
proc_lib:spawn_link(Module, Function, Args) -> pid()
proc_lib:spawn_link(Node, Module, Function, Args) -> pid()
proc_lib:spawn_opt(Module, Function, Args, SpawnOpts) -> pid()
proc_lib:spawn_opt(Node, Module, Func, Args, SpawnOpts) -> pid()

Powyższe funkcje nie różnią się od tych znanych wcześniej poza zapisywaniem w kontekście procesu dodatkowych informacji.

Synchroniczne

Poniższe funkcje startują proces i czekają, aż zgłosi on gotowość do działania:

proc_lib:start(Module, Function, Args) -> Ret
proc_lib:start(Module, Function, Args, Time) -> Ret
proc_lib:start(Module, Function, Args, Time, SpawnOpts) -> Ret
proc_lib:start_link(Module, Function, Args) -> Ret
proc_lib:start_link(Module, Function, Args, Time) -> Ret
proc_lib:start_link(Module, Function, Args, Time, SpawnOpts) -> Ret

Wystartowany proces musi potwierdzić, że został uruchomiony i zainicjalizowany:

proc_lib:init_ack(Parent, Ret) -> void()
proc_lib:init_ack(Ret) -> void()

Jeśli inicjalizacja nowego procesu się nie powiedzie powinien on wyjść z użyciem funkcji exit(Reason)

Przykład
start_link() ->
  proc_lib:start_link(procname, init, [self()]).
 
init(Parent) ->
  ...
  proc_lib:init_ack(Parent, {ok, self()}),
  loop(...).

Zamrażanie

erlang:hibernate(Module, Function, Args)

Zamraża proces powodując zmniejszenie jego zapotrzebowania na pamięć do minimum. Przydatne gdy proces będzie nieaktywny przez jakiś dłuższy czas - tj. nie będzie odbierał komunikatów. Proces zostaje odmrożony w momencie pojawienia się w jego skrzynce nowego komunikatu. Jeśli w momencie zamrażania skrzynka pocztowa procesu jest niepusta, proces zostaje natychmiastowo odmrożony.

Funkcja erlang:hibernate/3 nie wraca do wywołującego!

Zamrażanie procesu:

  • zeruje jego stos - tzn. funkcja przekazana jako argument staje się funkcją pierwotną procesu,
  • dokonuje odśmiecania pamięci - wszystkie żywe obiekty na stercie są układane w ciągłym obszarze pamięci,
  • ogranicza wielkość sterty do bloku żywych obiektów - nawet jeśli sterta miałaby być mniejsza niż to przewidziane dla tego procesu.

Odmrażanie procesu:

  • zwiększa stertę do minimalnego rozmiaru - ale tylko jeśli żywe obiekty zajmowały mniej miejsca,
  • rozpoczyna wykonywanie funkcji {Module,Function,Args}
proc_lib:hibernate(Module, Function, Args)

Powodem utworzenia tej funkcji jest interakcja erlang:hibernate/3 z obsługą wyjątków. Klauzula catch wkłada na stos informacje, które są wykorzystywane przy zwijaniu stosu. Jeśli opróżniliśmy stos to tej informacji nie ma i tracimy podprocedurę łapiącą wyjątek. Aby przywrócić obsługę wyjątków charakterystyczną dla procesów OTP należy wywoływać proc_lib:hibernate/3 zamiast erlang:hibernate/3.

Komunikaty systemowe OTP

Procesy nie impementujące standardowych zachowań OTP, a mające współpracować z takimi, muszą umieć obsługiwać pewne komunikaty.

  • Predefiniowane komunikaty systemowe postaci {system, From, Msg}. Proces nie powinien interpretować tych danych, tylko obsłużyć przy pomocy funkcji sys:handle_system_msg/6.
  • Komunikat zakończenia procesu. Jeśli proces wyłapuje sygnały, to musi obsługiwać te otrzymane od swojego rodzica. W tym przypadku otrzymuje od nadzorcy komunikat {'EXIT', Parent, Reason} i powinien zakończyć swoje działanie korzystając z funkcji exit/1 z powodem zakończenia z komunikatu (inaczej - całe drzewo powinno musi zakończyć swoje działanie z tym samym powodem).
  • Jeśli proces zakłada, że moduły użyte do jego implementacji będą dynamicznie podmieniane w trakcie jego działania, to musi rozumieć komunikat {get_modules,From}. Przykładem takie procesu jest gen_event. Odpowiedzią na taki komunikat powinna być krotka {modules, Modules}. Informacja ta jest używana w procesie wymiany lub zamrażania komponentów systemu.

Będziemy dalej posługiwać się dwoma typami, które warto zapamiętać:

  • DebugOpts - lista opcji odpluskwiania: trace | log | statistics | {log_to_file, FileName} | {install, {Func, FuncState}}
  • ProcRef - uchwyt do procesu: pid() | atom() | {global, atom()

Istnieje tylko jedna zdefiniowana postać komunikatu systemowego tj. {system, From, Msg} i jest ona wykorzystywana przez OTP. Programista ma oczywiście prawo definiować własne komunikaty systemowe i nie ma na to położonych żadnych ograniczeń. Natomiast istnieje konwencja, która mówi jak nazywać komunikaty systemowe mówiące o akcji odebrania lub wysłania komunikatu z procesu:

  • {in, Msg}
  • {in, Msg, From}
  • {out, Msg, To}

Interakcja z procesami OTP

Logowanie

sys:log(ProcRef,Flag) -> ok | {ok, [SysEvent]}

Gdzie Flag przyjmuje następujące wartości:

  • true / false - włącza lub wyłącza logowanie komunikatów systemowych,
  • {true, N} - włącza logowanie komunikatów systemowych z ograniczeniem, że może on być ustalonej długości,
  • get / print - pobiera i zwraca / drukuje komunikaty systemowe, a następnie opróżnia logi.
sys:log_to_file(ProcRef,FileName) -> ok | {error, open_file}

Ewentualnie możemy przekierować wszystkie komunikaty systemowe do pliku. Jeśli FileName == false logowanie zostaje wyłączone.

Śledzenie

sys:trace(ProcRef,Flag)

Śledzenie włącza drukowanie wszystkich komunikatów systemowych na standardowe wyjście.

Statystyki

sys:statistics(ProcRef,Flag) -> ok | {ok, Statistics}

Flag może przyjmować następujące wartości:

  • true / false - włącza / wyłącza zbieranie statystyk,
  • get - pobiera statystki.

Statystyki są w następującym formacie:

{start_time, {Date1, Time1}}
{current_time, {Date2, Time2}}
{reductions, integer()}
{messages_in, integer()}
{messages_out, integer()}

gdzie:

Date1 = Date2 = {Year, Month, Day}
Time1 = Time2 = {Hour, Min, Sec}

Zawieszanie, wymiana kodu, wznawianie

Zawieszanie i wznawianie działania procesu jest używane w trakcie wymiany modułu obsługi danego procesu. Kiedy proces jest zawieszony reaguje wyłącznie na komunikaty systemowe.

sys:suspend(ProcRef)
sys:resume(ProcRef)

Następujące wywołanie wymienia moduł obsługi procesu, o ile proces jest zawieszony.

sys:change_code(ProcRef, Module, OldVsn, Extra, Timeout) -> ok | {error, Reason}

Argument Extra przechowuje dodatkowe dane potrzebne przy aktualizacji stanu procesu, OldVsn przechowuje starą wersję modułu. W trakcie działania wywołuje funkcję Module:system_code_change/4.

Stan procesu

sys:get_status(ProcRef) -> {status, Pid, {module, Mod}, [ProcDict, SysState, Parent, Debug, Misc]}
  • SysState atom running lub suspended,
  • Misc zależy od implementacji procesu - wyciąga się ją z procesu przy pomocy funkcji Module:format_status/2.
Przykład
{status,<0.26.0>,{module,gen_server},
  [
    [
      {'$ancestors',[kernel_sup,<0.9.0>]},
      {'$initial_call',{kernel_config,init,1}}
    ],
    running,
    <0.10.0>,
    [],
    [
      {header, "Status for generic server <0.26.0>"},
      {data, [ {"Status",running}, {"Parent",<0.10.0>}, {"Logged events",[]} ] },
      {data, [ {"State",[]} ] }
    ]
  ]}

Procedury odpluskwiania

Możemy mieć potrzebę dodania funkcji, które będą modyfikowały stan procesu w zależności od przychodzących komunikatów systemowych.

Taka funkcja ma następującą postać:

fun(FuncState, SysEvent, ProcState) -> done | NewFuncState

Zostanie wywołana za każdym razem kiedy pojawi się zdarzenie systemowe. Funkcja zostanie usunięta, jeśli zwróci atom done lub jej wykonanie się nie powiedzie.

sys:install(ProcRef,{Func,FuncState})
sys:remove(ProcRef,Func)
sys:no_debug(ProcRef)

Taką procedurę odpluskwiającą możemy zainstalować w danym procesie lub usunąć ją z niego. Możemy też wyłączyć wszystkie funkcje odpluskwiające w danym procesie.

Funkcje wspomagające implementację procesu OTP

Odpluskwianie

sys:debug_options(DebugOpts) -> Debug

Mechanizm odpluskwiania wymaga wewnętrznej struktury danych reprezentowaną przez Debug. Przy pomocy powyższej funkcji można ją stworzyć na podstawie listy opcji.

sys:handle_debug(Debug,FormFunc,Extra,SysEvent) -> NewDebug

Powyższa funkcja powinna być wywoływana dla każdego komunikatu, którego śledzeniem lub logowanie jesteśmy zainteresowani.

FormFunc jest funkcją o następującej postaci:

fun(IoDevice, SysEvent, Extra)

Konwertuje ona komunikat systemowy do postaci drukowalnej, a następnie wysyła do urządzenia IoDevice - np. standardowe wyjście. Parametr Extra może przechowywać dodatkowe informacje przydatne przy formatowaniu komunikatu np. nazwę procesu lub element jego stanu wewnętrznego.

Należy wspomnieć, że parametry SysEvent i Extra są przekazywane bezpośrednio z funkcji handle_debug/4.

Przykład

Inicjalizacja struktury służącej do odpluskwiania:

init(Parent) ->
  ...
  Debug = sys:debug_options([]),
  ...
  loop(State, Parent, Debug).

Obsługa komunikatów, które będzie można ewentualnie logować:

loop(State, Parent, Debug) ->
  receive
    {From, alloc} ->
      Debug2 = sys:handle_debug(Deb, {?MODULE, write_debug}, ?MODULE, {in, alloc, From}),
      {Ch, NewState} = alloc(State),
      From ! {?MODULE, Ch},
      Debug3 = sys:handle_debug(Debug2, {?MODULE, write_debug}, ?MODULE, {out, {?MODULE, Ch}, From}),
      loop(NewState, Parent, Debug3);
 
    {free, Ch} ->
      Debug2 = sys:handle_debug(Deb, {?MODULE, write_debug}, ?MODULE, {in, {free, Ch}}),
      NewState = free(Ch, State),
      loop(NewState, Parent, Debug2);
    ...
  end.
 
write_debug(Dev, Event, Name) ->
  io:format(Dev, "~p event = ~p~n", [Name, Event]).

Obsługa komunikatów systemowych

sys:handle_system_msg(Msg,From,Parent,Module,Debug,Misc)

Powyższa funkcja zajmuje się przetwarzaniem komunikatów systemowych postaci {system, From, Msg}. Funkcja ta nigdy nie wraca i w zależności od komunikatu woła Module:system_continue/3 lub Module:system_terminate/4. Komunikatu nie należy interpretować. Misc jest argumentem pomocniczym i może posłużyć do przekazania bieżącego stanu procesu.

Przykład
loop(State, Parent, Debug) ->
  receive
    ...
    {system, From, Request} ->
      sys:handle_system_msg(Request, From, Parent, ?MODULE, Debug, State)
  end.
 
system_continue(Parent, NewDebug, State) ->
  loop(State, Parent, NewDebug).
 
system_terminate(Reason, Parent, NewDebug, State) ->
  exit(Reason).

Funkcje zwrotne procesu OTP

Wszystkie niżej wymienione funkcje są wywoływane z sys:handle_system_msg/6.

Kontynuowanie pracy

Module:system_continue(Parent, Debug, Misc)

Wywoływane kiedy proces powinien wznowić swoją pracę (np. po zawieszeniu). Ta funkcja nigdy nie wraca.

Zakończenie pracy

Module:system_terminate(Reason, Parent, Debug, Misc)

Wywoływane kiedy proces powinien się zakończyć (np. proces został zawieszony, a rodzic wymusza jego zakończenie) i posprzątać po sobie. Ta funkcja nigdy nie wraca.

Wymiana modułu obsługi

Module:system_code_change(ProcState, Module, OldVsn, Extra) -> {ok, NewProcState}

Wywoływane kiedy proces wymienia moduł obsługi i potrzebuje skonwertować swój stan wewnętrzny.

Źródła

 
erlang/otp.txt · Last modified: 2010/05/07 17:52 by Krystian Baclawski
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-No Derivative Works 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki