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 behaviour.
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]]}
This section describes how to write a process which comply to the OTP design principles, without making use of a behaviour. Such a process should:
sys
debug facilities, and
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 behaviours automatically understand these messages.
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]]}
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 some information, for example about the ancestors and initial call, that is needed for a process in a supervision tree.
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()]).
start_link
calls the function proc_lib:start_link
.
This functions spawns and links to a new process, executing
ch4:init(Pid)
, where Pid
is the pid of process
calling ch4:start_link()
(the parent process).
In init
, all initialization including name registration
is done. The 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.
To support the debug facilites in sys
, a term Deb
is initialized in init
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, the following function should be called:
sys:handle_debug(Deb, Func, Info, Event)
System events are:
{in, Msg}
or
{in, Msg, From}
.
{out, Msg, To}
.
Func
is a tuple {Module, Name}
(or a fun) and
should be a user defined function used to format trace output.
The function is called as Module:Name(Dev, Event, Info)
.
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]).
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 information.
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 behaviour 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.