6. sys and proc_lib

6 sys and proc_lib

sys模块具有对使用行为实现的流程进行简单调试的功能。它还具有与proc_lib模块中的功能一起用于实施符合OTP设计原则而不使用标准行为的特殊过程的功能。这些函数也可以用来实现用户定义的(非标准)行为。

sysproc_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,使用它来实现sysproc_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和初始调用)。

如果进程与另一个原因,而不是终止normalshutdown,产生崩溃报告。有关崩溃报告的更多信息,请参阅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每个传入和传出的消息都会被调用。格式化功能Funcch4: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)

预计监督树中的进程将以与其父进程相同的原因终止。

  • RequestFrom要从系统消息传递到调用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合同的子类型。