8 Writing an Application
This chapter describes and examplifies the following tasks, which all make up the larger task of writing an Erlang application:
- how to structure an application
- how to create a supervision tree
- how to use the common behaviours
- how to install event handlers
- how to configure an application
- how to write an application specification
- how to test an application
- how to write a distributed application.
8.1 Structuring the 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 thevshlr
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.
Illustration of HLR Application8.2 Designing the Processes
Each application has an application callback module, with behaviour
application
. This module is called when the application is started, and when it has stopped. Thestart
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
orgen_event
. Alternatively, they should be special processes written withsys
andproc_lib
. There are two main reasons for this:
- we want to be able to change code on all processes
- we want a fault tolerant system.
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 BIFsspawn
orspawn_link
. However, it will not be possible to change code in these processes.
Always use
spawn_link
, and neverspawn
. You might otherwise loose track of the process and produce a zombie process.The next example illustrates the following scenario:
- we want a special alarm event handler installed during the lifetime of the
hlr
application- this event handler is called
hlr_alarm_h
and writes each alarm to a special file- when starting
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]}}.8.2.1 Configuring the Application
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 thegen_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 callingcode: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 callapplication:get_env(hlr, hlr_alarm_file)
. Thehlr_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).8.2.2 Application Specification
An application specification is required in order to test the
hlr
application. This specification is placed in the filehlr.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 filehlr.alarms
in the current directory as a default.8.2.3 Testing the Application
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 calledsys.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]8.3 Distributed Applications
This section describes and illustrates how distributed applications can be used.
The example illustrated in this section is shown in .
Example of Distributed ApplicationThis system has the following components and characteristics:
- there are two administrative CPUs,
adm1
andadm2
- there are six functional CPUs,
fp1 - fp6
which are organized as shown in the illustration below- the two administrative CPUs are used for redundancy and we want to use both of them for performance reasons
- there are five applications of different type:
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 applicationsss7
andx25
. We want as many instances of this application as possible, but only one per node.ss7
. This application interfacesss7
. We want as many instances of this application as possible, but only one per node.x25
. This application interfacesx25
. There must only be one instance of this application in the system.The administrative CPUs take care of the management applications
snmp
andcmip
, and the functional CPUs the call handling applicationch
. This application uses the interfacesss7
andx25
, which are represented by corresponding applications. As shown in the figure, onlyfp1
andfp2
have an ss7 interface, and onlyfp3
andfp4
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
Node Distribution for Example Application The following sections describe how to specify this parameter for the different applications in the example.
8.3.1 The SNMP and CMIP Applications
These applications can run on either
adm1
oradm2
. In normal operating mode, we want one application to run at each of these processors,snmp
onadm1
, andcmip
onadm2
. 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
andcmip
is started on both nodes.8.3.2 The CH Application
The
ch
application is a local application and is started in the boot script on eachfp
node. This call handling application is run on eachfp
node and the application controller can therefore not view this application as distributed.8.3.3 The SS7 Application
This application also has several instances. For this reason, it cannot be distributed, but is local on nodes
fp1
andfp2
.8.3.4 The X25 Application
This is an application with one instance only and it can run on either
fp3
orfp4
. 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}]}