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 thevshlrserver 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. Thestartfunction 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_fsmorgen_event. Alternatively, they should be special processes written withsysandproc_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_linkinstead of the BIFsspawnorspawn_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
hlrapplication
- this event handler is called
hlr_alarm_hand 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
hlrwill 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
hlralarm event handler is used as an example of how to configure an application. This handler is an instance of thegen_eventbehaviour. 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.cnfcontains 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
privof 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_fileis 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_hlooks 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
hlrapplication. 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_fileis specified in the system configuration file, we use the filehlr.alarmsin 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
hlralarm 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,
adm1andadm2
- there are six functional CPUs,
fp1 - fp6which 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 applicationsss7andx25. 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
snmpandcmip, and the functional CPUs the call handling applicationch. This application uses the interfacesss7andx25, which are represented by corresponding applications. As shown in the figure, onlyfp1andfp2have an ss7 interface, and onlyfp3andfp4have an x25 interface.This is summarized in the table below:
Application Instances Nodes snmp1 adm1, adm2cmip1 adm1, adm2chN fp1 - fp6ss7N fp1, fp2x251 fp3, fp4Node 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
adm1oradm2. In normal operating mode, we want one application to run at each of these processors,snmponadm1, andcmiponadm2. 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,
snmpandcmipis started on both nodes.8.3.2 The CH Application
The
chapplication is a local application and is started in the boot script on eachfpnode. This call handling application is run on eachfpnode 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
fp1andfp2.8.3.4 The X25 Application
This is an application with one instance only and it can run on either
fp3orfp4. 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}]}