6 Sys and Proc_Lib
The module sys contains functions for simple debugging of processes implemented using behaviours.
There are also functions that, together with functions in the module proc_lib, can be used to implement a special process, a process which comply to the OTP design principles without making use of a standard behaviour. They can also be used to implement user defined (non-standard) behaviours.
Both sys and proc_lib belong to the STDLIB application.
6.1 Simple Debugging
The module sys contains some functions for simple debugging of processes implemented using behaviours. We use the code_lock example from the gen_event chapter to illustrate this:
% 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]]}
6.2 Special Processes
This section describes how to write a process which comply to the OTP design principles, without making use of a standard behaviour. Such a process should:
- be started in a way that makes the process fit into a supervision tree,
- support the sysdebug facilities, and
- take care of system messages.
System messages are messages with special meaning, used in the supervision tree. Typical system messages are requests for trace output, and requests to suspend or resume process execution (used during release handling). Processes implemented using standard behaviours automatically understand these messages.
Example
The simple server from the Overview chapter, implemented using sys and proc_lib so it fits into a supervision tree:
-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]).
Example on how the simple debugging functions in sys can be used for ch4 as well:
% 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]]}
Starting the Process
A function in the proc_lib module should be used to start the process. There are several possible functions, for example spawn_link/3,4 for asynchronous start and start_link/3,4,5 for synchronous start.
A process started using one of these functions will store information that is needed for a process in a supervision tree, for example about the ancestors and initial call.
Also, if the process terminates with another reason than normal or shutdown, a crash report (see SASL User's Guide) is generated.
In the example, synchronous start is used. The process is started by calling ch4:start_link():
start_link() -> proc_lib:start_link(ch4, init, [self()]).
ch4:start_link calls the function proc_lib:start_link. This function takes a module name, a function name and an argument list as arguments and spawns and links to a new process. The new process starts by executing the given function, in this case ch4:init(Pid), where Pid is the pid (self()) of the first process, that is the parent process.
In init, all initialization including name registration is done. The new process must also acknowledge that it has been started to the parent:
init(Parent) -> ... proc_lib:init_ack(Parent, {ok, self()}), loop(...).
proc_lib:start_link is synchronous and does not return until proc_lib:init_ack has been called.
Debugging
To support the debug facilites in sys, we need a debug structure, a term Deb which is initialized using sys:debug_options/1:
init(Parent) -> ... Deb = sys:debug_options([]), ... loop(Chs, Parent, Deb).
sys:debug_options/1 takes a list of options as argument. Here the list is empty, which means no debugging is enabled initially. See sys(3) for information about possible options.
Then for each system event that we want to be logged or traced, the following function should be called.
sys:handle_debug(Deb, Func, Info, Event) => Deb1
-
Deb is the debug structure.
-
Func is a tuple {Module, Name} (or a fun) and should specify a (user defined) function used to format trace output. For each system event, the format function is called as Module:Name(Dev, Event, Info), where:
-
Dev is the IO device to which the output should be printed. See io(3).
-
Event and Info are passed as-is from handle_debug.
-
-
Info is used to pass additional information to Func, it can be any term and is passed as-is.
-
Event is the system event. It is up to the user to define what a system event is and how it should be represented, but typically at least incoming and outgoing messages are considered system events and represented by the tuples {in,Msg[,From]} and {out,Msg,To}, respectively.
handle_debug returns an updated debug structure Deb1.
In the example, handle_debug is called for each incoming and outgoing message. The format function Func is the function ch4:write_debug/3 which prints the message using 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 event = ~p~n", [Name, Event]).
Handling System Messages
System messages are received as:
{system, From, Request}
The content and meaning of these messages do not need to be interpreted by the process. Instead the following function should be called:
sys:handle_system_msg(Request, From, Parent, Module, Deb, State)
This function does not return. It will handle the system message and then call:
Module:system_continue(Parent, Deb, State)
if process execution should continue, or:
Module:system_terminate(Reason, Parent, Deb, State)
if the process should terminate. Note that a process in a supervision tree is expected to terminate with the same reason as its parent.
- Request and From should be passed as-is from the system message to the call to handle_system_msg.
- Parent is the pid of the parent.
- Module is the name of the module.
- Deb is the debug structure.
- State is a term describing the internal state and is passed to system_continue/system_terminate.
In the example:
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).
If the special process is set to trap exits, note that if the parent process terminates, the expected behavior is to terminate with the same reason:
init(...) -> ..., process_flag(trap_exit, true), ..., loop(...). loop(...) -> receive ... {'EXIT', Parent, Reason} -> ..maybe some cleaning up here.. exit(Reason); ... end.
6.3 User-Defined Behaviours
To implement a user-defined behaviour, write code similar to code for a special process but calling functions in a callback module for handling specific tasks.
If it is desired that the compiler should warn for missing callback functions, as it does for the OTP behaviours, add callback attributes in the behaviour module to describe the expected callbacks:
-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.
where NameX are the names of the expected callbacks and ArgX_Y, ResX are types as they are described in Specifications for functions in Types and Function Specifications. The whole syntax of spec attributes is supported by callback attributes.
Alternatively you may directly implement and export the function:
behaviour_info(callbacks) -> [{Name1,Arity1},...,{NameN,ArityN}].
where each {Name,Arity} specifies the name and arity of a callback function. This function is otherwise automatically generated by the compiler using the callback attributes.
When the compiler encounters the module attribute -behaviour(Behaviour). in a module Mod, it will call Behaviour:behaviour_info(callbacks) and compare the result with the set of functions actually exported from Mod, and issue a warning if any callback function is missing.
Example:
%% User-defined behaviour module -module(simple_server). -export([start_link/2,...]). -callback init(State :: term()) -> 'ok'. -callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}. -callback terminate() -> 'ok'. %% 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, ...). ...
In a callback module:
-module(db). -behaviour(simple_server). -export([init/0, handle_req/2, terminate/0]). ...