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.
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 standard 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 standard 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 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.
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]).
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.
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, 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.
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,...]). -export([behaviour_info/1]). behaviour_info(callbacks) -> [{init,1}, {handle_req,1}, {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/1, terminate/0]). ...