This chapter describes and examplifies the following tasks, which all make up the larger task of writing an Erlang application:
Every application has an application master process that monitors the behaviour of the entire application. It starts the application by calling the start function specified in the application specification. This start function is assumed to start one process that is the main process of the application. Normally, this process is a supervisor, but it could also be a supervisor bridge.
Most applications are structured as a supervision tree, where the main process is the root supervisor of the application, and all other processes in the application are located somewhere under this supervisor.
To illustrate this point, suppose that we want to build an application named hlr
. We want this application to contain the vshlr
server introduced in Client-Server Principles, and a special alarm event handler. This simple application will then have one supervisor and one worker as shown in Illustration of HLR Application.
Each application has an application callback module, with behaviour application
. This module is called when the application is started, and when it has stopped. The start
function in this module starts the topmost supervisor for the application.
All worker processes in the application should be written using the standard behaviours, such as gen_server
, gen_fsm
or gen_event
. Alternatively, they should be special processes written with sys
and proc_lib
. There are two main reasons for this:
Simple, temporary processes can be written as normal Erlang processes without using a standard behaviour. However, these processes must be started with proc_lib:spawn_link
instead of the BIFs spawn
or spawn_link
. However, it will not be possible to change code in these processes.
![]() |
Always use |
The next example illustrates the following scenario:
hlr
application
hlr_alarm_h
and writes each alarm to a special file
hlr
, we want to install this event handler in the already existing alarm handler.
To implement this, we place the call gen_event:add_handler(alarm_handler, hlr_alarm_h, FileName)
in the initialisation function of the application.
The application callback module for hlr
will then look as follows:
-module(hlr). -vsn(1). -behaviour(application). %% External exports -export([start/2, stop/1]). %%%----------------------------------------------------------------- %%% This module implements the application HLR. %%%----------------------------------------------------------------- start(_, _) -> case hlr_sup:start() of {ok, Pid} -> gen_event:add_handler(alarm_handler, hlr_alarm_h, []), {ok, Pid, []}; Error -> Error end. stop(_State) -> gen_event:delete_handler(alarm_handler, hlr_alarm_h).
The supervisor will look as follows:
-module(hlr_sup). -vsn(1). -behaviour(supervisor). %% External exports -export([start/0]). %% Internal exports -export([init/1]). %%%----------------------------------------------------------------- %%% This module implements a supervisor for the HLR application. %%%----------------------------------------------------------------- start() -> supervisor:start_link({local, hlr_sup}, hlr_sup, []). init([]) -> SupFlags = {one_for_one, 4, 3600}, Vshlr = {xx2, {vshlr_2, start_link, []}, permanent, 2000, worker, [vshlr_2]}, {ok, {SupFlags, [Vshlr]}}.
In this section, the hlr
alarm event handler is used as an example of how to configure an application. This handler is an instance of the gen_event
behaviour. Its purpose is to write some alarms to a specified file. The event handler should be configured to write to the filename specified in the alarm output.
The file hlr_alarms.cnf
contains a list of all alarms which should be logged. This file looks as follows:
hlr_almost_full. hlr_inconsistent.
This file is stored in the private directory priv
of the application. It is found by calling code:priv_dir(hlr)
.
Each application has an associated environment where configuration parameters are defined. This environment is specified in the application specification, and is overridden by the system configuration file. The value of the parameter hlr_alarm_file
is a string which specifies the file which logs all alarms. The value of the parameter is found with the call application:get_env(hlr, hlr_alarm_file)
. The hlr_alarm_h
looks as follows:
-module(hlr_alarm_h). -vsn(1). -behaviour(gen_event). -export([init/1, handle_event/2, handle_info/2, terminate/2]). -record(state, {fd, alarms}). %%----------------------------------------------------------------- %% Callback functions from gen_event %%----------------------------------------------------------------- init(_) -> CnfFile = filename:join(code:priv_dir(hlr), "hlr_alarm.cnf"), Alarms = case file:consult(CnfFile) of {ok, List} -> List; _ -> [] end, case application:get_env(hlr, hlr_alarm_file) of {ok, File} -> {ok, Fd} = file:open(File, write), {ok, #state{fd = Fd, alarms = Alarms}}; undefined -> {error, {no_config, hlr_alarm_file}} end. handle_event({set_alarm, Alarm}, State)-> case is_hlr_alarm(Alarm, State) of true -> io:format(State#state.fd, "set alarm: ~p~n", [Alarm]); false -> ok end, {ok, State}; handle_event({clear_alarm, AlarmId}, State)-> case is_hlr_alarm(Alarm, State) of true -> io:format(State#state.fd, "clear alarm: ~p~n", [AlarmId]); false -> ok end, {ok, State}. handle_info(_, State) -> {ok, State}. terminate(_, State) -> file:close(State#state.fd). is_hlr_alarm({AlarmId, _}, #state{alarms = Alarms}) -> lists:member(AlarmId, Alarms).
An application specification is required in order to test the hlr
application. This specification is placed in the file hlr.app
, which looks as follows:
{application, hlr, [{description, "VSHLR"}, {vsn, "1.0"}, {modules, [{vshlr_2, 1}, {hlr_alarm_h, 1}, {hlr_sup, 1}, {hlr, 1}]}, {registered, [hlr_sup, xx2]}, {applications, [kernel, stdlib, sasl]}, {env, [{hlr_alarm_file, "hlr.alarms"}]}, {mod, {hlr, []}}]}.
This specification says that if no hlr_alarm_file
is specified in the system configuration file, we use the file hlr.alarms
in the current directory as a default.
The next task is to test the application. In doing so, we also want to specify another hlr
alarm file. We do this by writing a configuration file called sys.config
:
[{hlr, [{hlr_alarm_file, "alarms.log"}]}].
The following interaction shows how to test the application. The command to start the system is followed by a command to start the application itself.
erl -pa . -config ./sys 5> application:start(hlr, temporary). =PROGRESS REPORT==== 29-May-1996::14:04:05 === Supervisor: {local,hlr_sup} Started: [{pid,<0.54.0>}, {name,xx2}, {mfa,{vshlr_2,start_link,[]}}, {restart_type,permanent}, {shutdown,2000}, {child_type,worker}] ok 6> gen_event:which_handlers(alarm_handler). [hlr_alarm_h,alarm_handler] 7> vshlr_2:i_am_at(martin, home). ok 8> vshlr_2:find(martin). {at,home} 9> application:stop(hlr). ok 10> gen_event:which_handlers(alarm_handler). [alarm_handler]
This section describes and illustrates how distributed applications can be used.
The example illustrated in this section is shown in .
This system has the following components and characteristics:
adm1
and adm2
fp1 - fp6
which are organized as shown in the illustration below
snmp
. This is a management application, which interfaces an operator. There must be only one instance of this application in the system.
cmip
. This is a management application, which interfaces an operator. There must be only one instance of this application in the system.
ch
. This is a call handling application. It needs the applications ss7
and x25
. We want as many instances of this application as possible, but only one per node.
ss7
. This application interfaces ss7
. We want as many instances of this application as possible, but only one per node.
x25
. This application interfaces x25
. There must only be one instance of this application in the system.
The administrative CPUs take care of the management applications snmp
and cmip
, and the functional CPUs the call handling application ch
. This application uses the interfaces ss7
and x25
, which are represented by corresponding applications. As shown in the figure, only fp1
and fp2
have an ss7 interface, and only fp3
and fp4
have an x25 interface.
This is summarized in the table below:
Application | Instances | Nodes |
snmp
|
1 |
adm1, adm2
|
cmip
|
1 |
adm1, adm2
|
ch
|
N |
fp1 - fp6
|
ss7
|
N |
fp1, fp2
|
x25
|
1 |
fp3, fp4
|
The following sections describe how to specify this parameter for the different applications in the example.
These applications can run on either adm1
or adm2
. In normal operating mode, we want one application to run at each of these processors, snmp
on adm1
, and cmip
on adm2
. If one of the nodes goes down, the other node starts the application and both applications will run on one node. When the faulty node restarts, it takes over its application from the other node.
This arrangement is specified as follows:
{snmp, [adm1, adm2]}, {cmip, [adm2, adm1]}
In the boot script, snmp
and cmip
is started on both nodes.
The ch
application is a local application and is started in the boot script on each fp
node. This call handling application is run on each fp
node and the application controller can therefore not view this application as distributed.
This application also has several instances. For this reason, it cannot be distributed, but is local on nodes fp1
and fp2
.
This is an application with one instance only and it can run on either fp3
or fp4
. Accordingly, it is a distributed application. In normal operating mode, we do not care on which one of these two processors the application runs, but if this node goes down, the other node must start the application. There is no need to move the application back to the original node if it restarts. This requirement is expressed as follows:
{x25, [{fp3, fp4}]}