6. sys and proc_lib
6 sys and proc_lib
sys
模块具有对使用行为实现的流程进行简单调试的功能。它还具有与proc_lib
模块中的功能一起用于实施符合OTP设计原则而不使用标准行为的特殊过程的功能
。这些函数也可以用来实现用户定义的(非标准)行为。
sys
和proc_lib
属于STDLIB应用。
6.1 简单调试
sys
模块具有对使用行为实现的流程进行简单调试的功能。以下code_lock
示例gen_statem Behaviour
用于说明这一点:
Erlang/OTP 20 [DEVELOPMENT] [erts-9.0] [source-5ace45e] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V9.0 (abort with ^G)
1> code_lock:start_link([1,2,3,4]).
Lock
{ok,<0.63.0>}
2> sys:statistics(code_lock, true).
ok
3> sys:trace(code_lock, true).
ok
4> code_lock:button(1).
*DBG* code_lock receive cast {button,1} in state locked
ok
*DBG* code_lock consume cast {button,1} in state locked
5> code_lock:button(2).
*DBG* code_lock receive cast {button,2} in state locked
ok
*DBG* code_lock consume cast {button,2} in state locked
6> code_lock:button(3).
*DBG* code_lock receive cast {button,3} in state locked
ok
*DBG* code_lock consume cast {button,3} in state locked
7> code_lock:button(4).
*DBG* code_lock receive cast {button,4} in state locked
ok
Unlock
*DBG* code_lock consume cast {button,4} in state locked
*DBG* code_lock receive state_timeout lock in state open
Lock
*DBG* code_lock consume state_timeout lock in state open
8> sys:statistics(code_lock, get).
{ok,[{start_time,{{2017,4,21},{16,8,7}}},
{current_time,{{2017,4,21},{16,9,42}}},
{reductions,2973},
{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.63.0>,
{module,gen_statem},
[[{'$initial_call',{code_lock,init,1}},
{'$ancestors',[<0.61.0>]}],
running,<0.61.0>,[],
[{header,"Status for state machine code_lock"},
{data,[{"Status",running},
{"Parent",<0.61.0>},
{"Logged Events",[]},
{"Postponed",[]}]},
{data,[{"State",
{locked,#{code => [1,2,3,4],remaining => [1,2,3,4]}}}]}]]}
6.2 特殊过程
本节介绍如何编写符合OTP设计原则的流程,而不使用标准行为。这样一个过程是:
- 以一种使过程适合监督树的方式开始
系统消息是监督树中使用的具有特殊含义的消息。典型的系统消息是请求跟踪输出,并请求暂停或恢复流程执行(在释放处理期间使用)。使用标准行为实现的过程自动理解这些消息。
示例
简单的服务器Overview
,使用它来实现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,
system_get_state/1, system_replace_state/2]).
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, fun ch4:write_debug/3,
ch4, {in, alloc, From}),
{Ch, Chs2} = alloc(Chs),
From ! {ch4, Ch},
Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
ch4, {out, {ch4, Ch}, From}),
loop(Chs2, Parent, Deb3
{free, Ch} ->
Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
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).
system_get_state(Chs) ->
{ok, Chs}.
system_replace_state(StateFun, Chs) ->
NChs = StateFun(Chs),
{ok, NChs, NChs}.
write_debug(Dev, Event, Name) ->
io:format(Dev, "~p event = ~p~n", [Name, Event]).
关于如何将sys
模块中的简单调试功能用于以下示例的示例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
同步启动。
使用这些函数之一开始的进程存储监督树中进程所需的信息(例如,关于ancestors和初始调用)。
如果进程与另一个原因,而不是终止normal
或shutdown
,产生崩溃报告。有关崩溃报告的更多信息,请参阅SASL用户指南。
在这个例子中,使用了同步启动。该过程首先调用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
,需要调试结构
。该Deb
术语使用sys:debug_options/1
以下内容初始化:
init(Parent) ->
...
Deb = sys:debug_options([]),
...
loop(Chs, Parent, Deb).
sys:debug_options/1
以选项列表作为参数。这里列表是空的,这意味着最初没有启用调试。有关可能的选项的信息,请参阅sys(3)
STDLIB中的手册页。
然后,对于要记录或跟踪的每个系统事件
,将调用以下函数。
sys:handle_debug(Deb, Func, Info, Event) => Deb1
这里:
Deb
是调试结构。
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, fun ch4:write_debug/3,
ch4, {in, alloc, From}),
{Ch, Chs2} = alloc(Chs),
From ! {ch4, Ch},
Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
ch4, {out, {ch4, Ch}, From}),
loop(Chs2, Parent, Deb3
{free, Ch} ->
Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
ch4, {in, {free, Ch}}),
Chs2 = free(Ch, Chs),
loop(Chs2, Parent, Deb2
...
end.
write_debug(Dev, Event, Name) ->
io:format(Dev, "~p event = ~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
。
如果进程要返回它的状态,handle_system_msg
调用:
Module:system_get_state(State)
如果该过程是使用乐趣来替换其状态StateFun
,则handle_system_msg
调用:
Module:system_replace_state(StateFun, State)
在这个例子中:
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).
system_get_state(Chs) ->
{ok, Chs, Chs}.
system_replace_state(StateFun, Chs) ->
NChs = StateFun(Chs),
{ok, NChs, NChs}.
如果特殊进程设置为陷阱退出并且父进程终止,则预期行为将以相同原因终止:
init(...) ->
...,
process_flag(trap_exit, true),
...,
loop(...).
loop(...) ->
receive
...
{'EXIT', Parent, Reason} ->
..maybe some cleaning up here..
exit(Reason
...
end.
6.3用户定义的行为
要实现用户定义的行为,请编写类似于特定进程代码的代码,但在回调模块中调用函数以处理特定任务。
如果编译器要警告缺少回调函数(如为OTP行为所做的那样),请-callback
在行为模块中添加属性以描述预期的回调函数:
-callback Name1(Arg1_1, Arg1_2, ..., Arg1_N1) -> Res1.
-callback Name2(Arg2_1, Arg2_2, ..., Arg2_N2) -> Res2.
...
-callback NameM(ArgM_1, ArgM_2, ..., ArgM_NM) -> ResM.
NameX
是预期回调的名称。ArgX_Y
并且ResX
是它们在其中描述的类型Types and Function Specifications
。该-spec
属性的全部语法都由该-callback
属性支持。
用于实现行为的用户可选的回调函数通过使用-optional_callbacks
属性来指定:
-optional_callbacks([OptName1/OptArity1, ..., OptNameK/OptArityK]).
每个OptName/OptArity
指定回调函数的名称和形式。请注意,该-optional_callbacks
属性将与该-callback
属性一起使用;它不能与behaviour_info()
下面描述的功能组合。
需要了解可选回调函数的工具可以调用Behaviour:behaviour_info(optional_callbacks)
以获取所有可选回调函数的列表。
注意
我们建议使用-callback
属性而不是behaviour_info()
函数。原因是额外的类型信息可以被工具用来生成文档或查找差异。
作为一种替代-callback
和-optional_callbacks
属性时,可能直接实现和出口behaviour_info()
:
behaviour_info(callbacks) ->
[{Name1, Arity1},...,{NameN, ArityN}].
每个{Name, Arity}
指定回调函数的名称和形式。这个函数否则由编译器使用-callback
属性自动生成。
当编译器遇到模块-behaviour(Behaviour).
中的模块属性时Mod
,它会调用Behaviour:behaviour_info(callbacks)
结果并将其与实际导出的函数集进行比较Mod
,并在缺少回调函数时发出警告。
示例:
%% User-defined behaviour module
-module(simple_server).
-export([start_link/2, init/3, ...]).
-callback init(State :: term()) -> 'ok'.
-callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}.
-callback terminate() -> 'ok'.
-callback format_state(State :: term()) -> term().
-optional_callbacks([format_state/1]).
%% Alternatively you may define:
%%
%% -export([behaviour_info/1]).
%% behaviour_info(callbacks) ->
%% [{init,1},
%% {handle_req,2},
%% {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/1, handle_req/2, terminate/0]).
...
-callback
通过-spec
在回调模块中添加属性,可以进一步细化通过行为模块中的属性指定的合同。这可能很有用,因为-callback
合同通常是通用的。具有回调合约的相同回调模块:
-module(db).
-behaviour(simple_server).
-export([init/1, handle_req/2, terminate/0]).
-record(state, {field1 :: [atom()], field2 :: integer()}).
-type state() :: #state{}.
-type request() :: {'store', term(), term()};
{'lookup', term()}.
...
-spec handle_req(request(), state()) -> {'ok', term()}.
...
每份-spec
合同都将成为相应-callback
合同的子类型。