OTP Sys Proc Lib
Материал из Erlang по-русски.
| Оригинал этой статьи находится по адресу Sys and Proc_Lib |
Автор: Mirrorer
Дата: 06.11.2006
Версия: 1.02
Модуль sys содержит функции для простейшей отладки процессов, реализованных с использованием поведений.
Существуют также такие функции, которые совместно с функциями в модуле proc_lib могут быть использованы для реализации специальных процессов (special process), процессов, которые соответствуют принципам дизайна OTP, но при этом не используют стандартных поведений. Они могут использоваться для реализации пользовательских (нестандартных) поведений.
И sys, и proc_lib являются частью приложения STDLIB.
Содержание |
Простая отладка
Модуль sys содержит некоторые функции для простой отладки процессов, реализованных с использованием поведений. Мы используем пример code_lock из главы Пример, чтобы это продемонстрировать:
% erl
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
Eshell V5.2.3.6 (abort with ^G)
1> code_lock:start_link([1,2,3,4]).
{ok,<0.32.0>}
2> sys:statistics(code_lock, true).
ok
3> sys:trace(code_lock, true).
ok
4> code_lock:button(4).
*DBG* code_lock got event {button,4} in state closed
ok
*DBG* code_lock switched to state closed
5> code_lock:button(3).
*DBG* code_lock got event {button,3} in state closed
ok
*DBG* code_lock switched to state closed
6> code_lock:button(2).
*DBG* code_lock got event {button,2} in state closed
ok
*DBG* code_lock switched to state closed
7> code_lock:button(1).
*DBG* code_lock got event {button,1} in state closed
ok
OPEN DOOR
*DBG* code_lock switched to state open
*DBG* code_lock got event timeout in state open
CLOSE DOOR
*DBG* code_lock switched to state closed
8> sys:statistics(code_lock, get).
{ok,[{start_time,{{2003,6,12},{14,11,40}}},
{current_time,{{2003,6,12},{14,12,14}}},
{reductions,333},
{messages_in,5},
{messages_out,0}]}
9> sys:statistics(code_lock, false).
ok
10> sys:trace(code_lock, false).
ok
11> sys:get_status(code_lock).
{status,<0.32.0>,
{module,gen_fsm},
[[{'$ancestors',[<0.30.0>]},
{'$initial_call',{gen,init_it,
[gen_fsm,
<0.30.0>,
<0.30.0>,
{local,code_lock},
code_lock,
[1,2,3,4],
[]]}}],
running,
<0.30.0>,
[],
[code_lock,closed,{[],[1,2,3,4]},code_lock,infinity]]}
Специальные процессы
Этот раздел описывает создание процессов, соответствующих принципам дизайна OTP, без использования стандартных поведений. Такие процессы должны:
- запускаться таким образом, чтобы процесс стал частью дерева контроля,
- поддерживать sys отладочные возможности, и
- позаботиться об обработке системных сообщений.
Системные сообщения – это сообщения, имеющие специальное назначение, используемые в дереве контроля. Типичные системные сообщения – это запросы на вывод данных, а также запросы на временную остановку или повторный запуск процессов (используемые при работе с релизами). Процессы, реализованные с помощью стандартных поведений, автоматически понимают эти сообщения.
Пример
Простой сервер из главы "Обзор", реализованный с использованием sys и proc_lib , следующим образом попадает в дерево контроля:
-module(ch4).
-export([start_link/0]).
-export([alloc/0, free/1]).
-export([init/1]).
-export([system_continue/3, system_terminate/4,
write_debug/3]).
start_link() ->
proc_lib:start_link(ch4, init, [self()]).
alloc() ->
ch4 ! {self(), alloc},
receive
{ch4, Res} ->
Res
end.
free(Ch) ->
ch4 ! {free, Ch},
ok.
init(Parent) ->
register(ch4, self()),
Chs = channels(),
Deb = sys:debug_options([]),
proc_lib:init_ack(Parent, {ok, self()}),
loop(Chs, Parent, Deb).
loop(Chs, Parent, Deb) ->
receive
{From, alloc} ->
Deb2 = sys:handle_debug(Deb, {ch4, write_debug},
ch4, {in, alloc, From}),
{Ch, Chs2} = alloc(Chs),
From ! {ch4, Ch},
Deb3 = sys:handle_debug(Deb2, {ch4, write_debug},
ch4, {out, {ch4, Ch}, From}),
loop(Chs2, Parent, Deb3);
{free, Ch} ->
Deb2 = sys:handle_debug(Deb, {ch4, write_debug},
ch4, {in, {free, Ch}}),
Chs2 = free(Ch, Chs),
loop(Chs2, Parent, Deb2);
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent,
ch4, Deb, Chs)
end.
system_continue(Parent, Deb, Chs) ->
loop(Chs, Parent, Deb).
system_terminate(Reason, Parent, Deb, Chs) ->
exit(Reason).
write_debug(Dev, Event, Name) ->
io:format(Dev, "~p event = ~p~n", [Name, Event]).
Пример использования простых отладочных функций может быть использован также для ch4 :
% erl
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
Eshell V5.2.3.6 (abort with ^G)
1> ch4:start_link().
{ok,<0.30.0>}
2> sys:statistics(ch4, true).
ok
3> sys:trace(ch4, true).
ok
4> ch4:alloc().
ch4 event = {in,alloc,<0.25.0>}
ch4 event = {out,{ch4,ch1},<0.25.0>}
ch1
5> ch4:free(ch1).
ch4 event = {in,{free,ch1}}
ok
6> sys:statistics(ch4, get).
{ok,[{start_time,{{2003,6,13},{9,47,5}}},
{current_time,{{2003,6,13},{9,47,56}}},
{reductions,109},
{messages_in,2},
{messages_out,1}]}
7> sys:statistics(ch4, false).
ok
8> sys:trace(ch4, false).
ok
9> sys:get_status(ch4).
{status,<0.30.0>,
{module,ch4},
[[{'$ancestors',[<0.25.0>]},{'$initial_call',{ch4,init,[<0.25.0>]}}],
running,
<0.25.0>,
[],
[ch1,ch2,ch3]]}
Запуск процессов
Для запуска процесса должна быть использована функция из модуля proc_lib. Есть несколько функций, подходящих для этой цели, например spawn_link/3,4 для асинхронного запуска и start_link/3,4,5 для синхронного.
Процесс запускается, используя одну из этих функций для сохранения информации, необходимой процессу в дереве контроля, например о потомках и первом вызове.
Например, используется синхронный запуск. Процесс запускается вызовом ch4:start_link():
start_link() -> proc_lib:start_link(ch4, init, [self()]).
ch4:start_link вызывает функцию proc_lib:start_link. Эта функция получает в качестве аргументов имя модуля, имя функции и список аргументов, создает новый процесс и устанавливает связь с ним. Новый процесс запускается вызовом заданной функции, в нашем случае ch4:init(Pid), где Pid это pid (self()) первого процесса, который является родительским процессом.
В функции init,производится вся инициализация, включая регистрацию имени. Новый процесс должен также уведомить родителя о своем успешном старте.
init(Parent) ->
...
proc_lib:init_ack(Parent, {ok, self()}),
loop(...).
proc_lib:start_link – синхронная функция, и не вернет управление до тех пор, пока не будет вызвана proc_lib:init_ack.
Отладка
Для поддержки отладочных возможностей в sys, нам нужна отладочная структура(debug structure), терм Deb, который инициализируется с помощью функции sys:debug_options/1:
init(Parent) ->
...
Deb = sys:debug_options([]),
...
loop(Chs, Parent, Deb).
sys:debug_options/1 принимает список опций в качестве аргумента. Если список пуст, это означает, что отладка не включена. Смотрите документацию по sys(3) для получения информации о возможных опциях.
После этого, для каждого системного события, которое мы хотим залогировать или оттрасировать, должна быть вызвана следующая функция :
sys:handle_debug(Deb, Func, Info, Event) => Deb1
- Deb – это отладочная структура.
- Func – это кортеж {Module, Name} (или функция) и должен указывать на (определенную пользователем) функцию для форматирования трассировочных данных. Для каждого системного события, функция форматирования вызывается так : Module:Name(Dev, Event, Info), где:
- Dev устройство ввода-вывода, на которое будет осуществлен вывод данных. См. io(3).
- Event и Info передаются в неизменяемом виде из handle_debug.
- Info использутся для передачи дополнительной информации в Func, это может быть любой терм, и он передается в первозданном виде.
- Event – это системное событие. Пользователь определяет, чем является системное событие и как оно должно быть представлено, но обычно по крайней мере входящие и исходящие сообщения соответствуют системным сообщениям и представляются кортежами {in,Msg[,From]} и {out,Msg,To} соответственно.
handle_debug возвращает обновленную отладочную структуру Deb1.
В этом примере handle_debug вызвается при каждом входящем и исходящем сообщениях. Функция форматирования Func – это функция ch4:write_debug/3, которая выводит сообщение, используя io:format/3.
loop(Chs, Parent, Deb) ->
receive
{From, alloc} ->
Deb2 = sys:handle_debug(Deb, {ch4, write_debug},
ch4, {in, alloc, From}),
{Ch, Chs2} = alloc(Chs),
From ! {ch4, Ch},
Deb3 = sys:handle_debug(Deb2, {ch4, write_debug},
ch4, {out, {ch4, Ch}, From}),
loop(Chs2, Parent, Deb3);
{free, Ch} ->
Deb2 = sys:handle_debug(Deb, {ch4, write_debug},
ch4, {in, {free, Ch}}),
Chs2 = free(Ch, Chs),
loop(Chs2, Parent, Deb2);
...
end.
write_debug(Dev, Event, Name) ->
io:format(Dev, "~p событие = ~p~n", [Name, Event]).
Обработка системных сообщений
Системное сообщение поступает в таком виде :
{system, From, Request}
Содержимое и смысл этих сообщений не должны как-либо обрабатываться процессом. Вместо этого должна быть вызвана следующая функция :
sys:handle_system_msg(Request, From, Parent, Module, Deb, State)
Эта функция не должна возвращать управление. Она обработает системное сообщение и затем вызовет:
Module:system_continue(Parent, Deb, State)
если процесс должен продолжать свою работу, или:
Module:system_terminate(Reason, Parent, Deb, State)
если процесс должен завершить свою работу. Заметьте, что процесс в дереве контроля должен завершить свою работу по той же причине, что и родитель.
- Request и From должны передаваться в неизменном виде от системного сообщения до вызова handle_system_msg.
- Parent – это pid родителя.
- Module – это имя модуля.
- Deb – это отладочная структура.
- State – это терм, описывающий внутреннее состояние, и он передается в system_continue/system_terminate.
В примере :
loop(Chs, Parent, Deb) ->
receive
...
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent,
ch4, Deb, Chs)
end.
system_continue(Parent, Deb, Chs) ->
loop(Chs, Parent, Deb).
system_terminate(Reason, Parent, Deb, Chs) ->
exit(Reason).
Если специальный процесс сконфигурирован так, чтобы отлавливать сообщения о завершении работы, необходимо заметить, что если родительский процесс завершает свою работу, ожидаемым поведением для процесса будет завершить свою работу с тем же сообщением.
init(...) ->
...,
process_flag(trap_exit, true),
...,
loop(...).
loop(...) ->
receive
...
{'EXIT', Parent, Reason} ->
..maybe some cleaning up here..
exit(Reason);
...
end.
Пользовательские поведения
Для реализации пользовательского поведения напишите код, подобный коду специального процесса, но вызывающий функции из модуля обратного вызова для выполнения определенных действий.
Если вы хотите, чтобы компилятор предупреждал об отсутствии функций обратного вызова, точно так же, как для поведений OTP, реализуйте и экспортируйте следующую функцию :
behaviour_info(callbacks) ->
[{Name1,Arity1},...,{NameN,ArityN}].
где каждый из кортежей {Name,Arity} определяет имя и арность функции обратного вызова.
Когда компилятор встречает атрибут модуля -behaviour(Behaviour) в модуле Mod, он вызывает Behaviour:behaviour_info(callbacks) и сравнивает результат с множеством функций, экспортированных из , и сообщает об ошибке, если какая-либо функция обратного вызова отсутствует.
Пример:
%% User-defined behaviour module
-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, ...).
...
-module(db). -behaviour(simple_server). -export([init/0, handle_req/1, terminate/0]).


