檢視原始碼 sys 與 proc_lib
sys
模組具有用於簡單除錯使用行為實作的進程的函式。它還具有一些函式,這些函式與 proc_lib
模組中的函式一起使用,可以實作一個符合 OTP 設計原則的特殊進程,而無需使用標準行為。這些函式還可以用於實作使用者定義(非標準)的行為。
sys
和 proc_lib
都屬於 STDLIB 應用程式。
簡單除錯
sys
模組具有用於簡單除錯使用行為實作的進程的函式。來自 gen_statem 行為的 code_lock
範例用於說明這一點
Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
1> code_lock:start_link([1,2,3,4]).
Lock
{ok,<0.90.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 => open
*DBG* code_lock start_timer {state_timeout,10000,lock,[]} in state open
*DBG* code_lock receive state_timeout lock in state open
Lock
*DBG* code_lock consume state_timeout lock in state open => locked
8> sys:statistics(code_lock, get).
{ok,[{start_time,{{2024,5,3},{8,11,1}}},
{current_time,{{2024,5,3},{8,11,48}}},
{reductions,4098},
{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.90.0>,
{module,gen_statem},
[[{'$initial_call',{code_lock,init,1}},
{'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>,
<0.64.0>,kernel_sup,<0.47.0>]}],
running,<0.88.0>,[],
[{header,"Status for state machine code_lock"},
{data,[{"Status",running},
{"Parent",<0.88.0>},
{"Modules",[code_lock]},
{"Time-outs",{0,[]}},
{"Logged Events",[]},
{"Postponed",[]}]},
{data,[{"State",
{locked,#{code => [1,2,3,4],
length => 4,buttons => []}}}]}]]}
特殊進程
本節介紹如何編寫符合 OTP 設計原則的進程,而無需使用標準行為。這樣的進程應:
系統訊息是具有特殊含義的訊息,在監督樹中使用。典型的系統訊息是追蹤輸出請求,以及暫停或恢復進程執行的請求(在發布處理期間使用)。使用標準行為實作的進程會自動理解這些訊息。
範例
以下是來自 概述的簡單伺服器,使用 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/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
1> ch4:start_link().
{ok,<0.90.0>}
2> sys:statistics(ch4, true).
ok
3> sys:trace(ch4, true).
ok
4> ch4:alloc().
ch4 event = {in,alloc,<0.88.0>}
ch4 event = {out,{ch4,1},<0.88.0>}
1
5> ch4:free(ch1).
ch4 event = {in,{free,ch1}}
ok
6> sys:statistics(ch4, get).
{ok,[{start_time,{{2024,5,3},{8,26,13}}},
{current_time,{{2024,5,3},{8,26,49}}},
{reductions,202},
{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.90.0>,
{module,ch4},
[[{'$initial_call',{ch4,init,1}},
{'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>,
<0.64.0>,kernel_sup,<0.47.0>]}],
running,<0.88.0>,[],
{[1],[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19|...]}]}
啟動進程
要使用 proc_lib
模組中的函式來啟動進程。有多個可用的函式,例如,proc_lib:spawn_link/3,4
用於非同步啟動,proc_lib:start_link/3,4,5
用於同步啟動。
當進程透過其中一個函式啟動時,會儲存監督樹內進程所需的資訊,例如有關祖先和初始呼叫的詳細資訊。
如果進程終止的原因不是 normal
或 shutdown
,則會產生崩潰報告。有關崩潰報告的更多資訊,請參閱 Kernel 使用者指南中的記錄。
在範例中,使用同步啟動。進程透過呼叫 ch4:start_link()
開始
start_link() ->
proc_lib:start_link(ch4, init, [self()]).
ch4:start_link/0
呼叫 proc_lib:start_link/3
,它會將模組名稱、函式名稱和引數列表作為引數。然後它會產生一個新的進程並建立連結。新進程從執行給定的函式開始,此處為 ch4:init(Pid)
,其中 Pid
是父進程的 pid(透過呼叫 self()
在呼叫 proc_lib:start_link/3
中取得)。
所有初始化(包括名稱註冊)都在 init/1
中完成。新進程必須向父進程確認它已啟動
init(Parent) ->
...
proc_lib:init_ack(Parent, {ok, self()}),
loop(...).
proc_lib:start_link/3
是同步的,並且在 proc_lib:init_ack/1,2
或 proc_lib:init_fail/2,3
被呼叫,或者進程已退出之前不會返回。
除錯
為了支援 sys
中的除錯功能,需要一個除錯結構。Deb
詞彙使用 sys:debug_options/1
初始化
init(Parent) ->
...
Deb = sys:debug_options([]),
...
loop(Chs, Parent, Deb).
sys:debug_options/1
採用選項列表。在此範例中給定一個空列表表示除錯最初處於停用狀態。有關可能選項的資訊,請參閱 STDLIB 中的sys
。
對於要記錄或追蹤的每個系統事件,都應呼叫以下函式
sys:handle_debug(Deb, Func, Info, Event) => Deb1
引數具有以下含義
Deb
是從sys:debug_options/1
返回的除錯結構。Func
是一個 fun,指定一個(使用者定義的)函式,用於格式化追蹤輸出。對於每個系統事件,格式化函式會以Func(Dev, Event, Info)
的形式呼叫,其中Dev
是要將輸出列印到的 I/O 裝置。請參閱 STDLIB 中的io
。Event
和Info
會從呼叫sys:handle_debug/4
依原樣傳遞。
Info
用於將更多資訊傳遞到Func
。它可以是任何詞彙,並會依原樣傳遞。Event
是系統事件。由使用者定義系統事件是什麼以及如何表示。通常,至少將傳入和傳出訊息視為系統事件,並分別以元組{in,Msg[,From]}
和{out,Msg,To[,State]}
表示。
sys:handle_debug/4
會返回更新後的除錯結構 Deb1
。
在範例中,會針對每個傳入和傳出訊息呼叫 sys:handle_debug/4
。格式化函式 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)
引數具有以下含義
- 接收到的系統訊息中的
Request
和From
會依原樣傳遞到呼叫sys:handle_system_msg/6
。 Parent
是父進程的 pid。Module
是實作特殊進程的模組名稱。Deb
是除錯結構。State
是描述內部狀態的詞彙,並傳遞到Module:system_continue/3
、Module:system_terminate/4
、Module:system_get_state/1
和Module:system_replace_state/2
。
sys:handle_system_msg/6
不會返回。它會處理系統訊息,並最終呼叫以下任一函式
Module:system_continue(Parent, Deb, State)
- 如果要繼續執行進程。Module:system_terminate(Reason, Parent, Deb, State)
- 如果要終止進程。
在處理系統訊息時,sys:handle_system_msg/6
可以呼叫以下函式之一
Module:system_get_state(State)
- 如果進程要返回其狀態。Module:system_replace_state(StateFun, State)
- 如果進程要使用 funStateFun
替換其狀態。有關更多資訊,請參閱sys:replace_state/3
。system_code_change(Misc, Module, OldVsn, Extra)
- 如果進程要執行程式碼變更。
預期監督樹中的進程會以與其父進程相同的原因終止。
在範例中,系統訊息由以下程式碼處理
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}.
如果特殊進程設定為捕獲結束,則它必須注意來自其父進程的 'EXIT' 訊息,並在父進程終止後使用相同的結束原因終止。
以下是一個範例
init(Parent) ->
...,
process_flag(trap_exit, true),
...,
loop(Parent).
loop(Parent) ->
receive
...
{'EXIT', Parent, Reason} ->
%% Clean up here, if needed.
exit(Reason);
...
end.
使用者定義的行為
要實作使用者定義的行為,請編寫類似於特殊進程程式碼的程式碼,但呼叫回呼模組中的函式來處理特定任務。
如果要讓編譯器針對遺失的回呼函式發出警告(如針對 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
是 類型與函式規格中所述的類型。-callback
屬性支援 -spec
屬性的完整語法。
使用者可以選擇實作的行為的可選回呼函式由使用 -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
屬性自動產生。
當編譯器在模組 Mod
中遇到模組屬性 -behaviour(Behaviour).
時,它會呼叫 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
合約的子類型。