[Erlang Systems]

8 Writing an Application

This chapter describes and examplifies the following tasks, which all make up the larger task of writing an Erlang 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 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.

hlr_sup
Illustration of HLR Application

8.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. 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.

Note!

Always use spawn_link, and never spawn. You might otherwise loose track of the process and produce a zombie process.

The next example illustrates the following scenario:

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 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).

8.2.2 Application Specification

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.

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 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]

8.3 Distributed Applications

This section describes and illustrates how distributed applications can be used.

The example illustrated in this section is shown in .

dist_app
Example of Distributed Application

This system has the following components and characteristics:

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
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 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.

8.3.2 The CH Application

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.

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 and fp2.

8.3.4 The X25 Application

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}]}
      

Copyright © 1991-1999 Ericsson Utvecklings AB