Приложения OTP

Материал из Erlang по-русски.

Оригинал этой статьи находится по адресу Applications

Перевод: К. Заборский

Дата: 12.11.2006

Версия: 1.0


Эту главу стоит читать вместе с app(4) и application(3).

Содержание

Концепция приложений

Когда у нас есть написанный код, реализующий какую-то специфическую функциональность, возможно мы захотим сделать из этого кода приложение - компонент, который можно запускать и останавливать как единое целое, и который также может быть использован повторно в других системах.

Чтобы сделать это, мы создаём модуль обратного вызова для приложения, где мы описываем, как это приложение следует запускать и останавливать.

Затем, нужна спецификация приложения, которую помещают в файл ресурсов приложения. Кроме того, надо указать из каких модулей состоит приложение и имя модуля обратного вызова.

Если использовать systools, утилиты Erlang/OTP для организации кода (см. Релизы), код каждого приложения помещают в отдельную папку в соответствии с предопределённой структурой каталогов.

Модуль обратного вызова для приложения

Как запустить и остановить код приложения, т.е. его дерево надзора, описывается двумя функциями обратного вызова:

start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
stop(State)   

Функция start вызывается для запуска приложения, она должна создать дерево надзора при помощи запуска супервайзора высшего уровня. Она должна вернуть pid супервайзора высшего уровня и опциональный терм State, который по умолчанию есть пустой список []. Этот терм передаётся "as-is" функции stop.

Параметр StartType обычно содержит атом normal. Он содержит другие значения только в случае передачи управления и восстановления после сбоя, см. Распределённые приложения (Distributed Applications). Параметр StartArgs определяется ключом mod в файле ресурсов приложения.

Функция stop/1 вызывается после того, как приложение было остановлено и необходимо выполнить необходимую очистку после этого. Следует отметить, что непосредственная остановка приложения, которая состоит в остановке дерева контроля, обрабатывается автоматически, как это описано в части о запуске и остановке приложений.

Пример модуля обратного вызова приложения, содержащего дерево надзора из главы о поведении супервайзора:

-module(ch_app).
-behaviour(application).

-export([start/2, stop/1]).

start(_Type, _Args) ->
    ch_sup:start_link().

stop(_State) ->
    ok.    

Библиотечное приложение, которое нельзя запускать или останавливать, не должно содержать модуль обратного вызова приложения.

Файл ресурсов приложения

Чтобы определить приложение, мы создаём спецификацию приложения, которая помещается в файл ресурсов приложения, или, короче, в .app-файл:

{application, Application, [Opt1,...,OptN]}.    

Application - это атом, являющийся именем приложения. Файл должен называться Application.app.

Каждая переменная Opt - это тупл вида {Key, Value}, который определяет определённое свойство приложения. Все ключи опциональны. Для неуказанных ключей используются значения по умолчанию.

Содержимое минимального .app-файла для библиотечного приложения libapp выглядит следующим образом:

{application, libapp, []}.    

Содержимое минимального .app-файла ch_app.app для приложения дерева надзора ch_app выглядит как:

{application, ch_app,
 [{mod, {ch_app,[]}}]}.    

Ключ mod определяет модуль обратного вызова и начальный аргумент приложения, в этом случае ch_app и [] соответственно. Это значит, что

ch_app:start(normal, [])   

будет вызвано, когда должно быть запущено приложение, и

ch_app:stop([])

будет вызвано, когда приложение должно быть остановлено.

При использовании systools, утилит Erlang/OTP для упаковки ??? кода (см. Releases), ключи description, vsn, modules, registered и applications должны быть указаны:

{application, ch_app,
 [{description, "Channel allocator"},
  {vsn, "1"},
  {modules, [ch_app, ch_sup, ch3]},
  {registered, [ch3]},
  {applications, [kernel, stdlib, sasl]},
  {mod, {ch_app,[]}}
 ]}.    

description

Короткое описание, строка. Значение по умолчанию - "".

vsn

Номер версии, строка. Значение по умолчанию - "".

modules

Все модули, привносимые приложением. systools использует этот список для генерации boot-скриптов и tar-файлов. Модуль должен быть определён в одном и только одном приложении. Значение по умолчанию - [].

registered

Все имена зарегистрированных в приложении процессов. systools использует этот список для обнаружения конфликтов имён между приложениями. Значение по умолчанию - [].

applications

Все приложения, которые должны быть запущены до запуска этого приложения. systools использует этот список для создания корректных boot-скриптов. Значение по умолчанию - [], но заметьте, что все приложения зависят, как минимум, от kernel и stdlib.

Синтаксис и содержимое файла ресурсов приложения детально описаны в app(4).

Структура каталогов

При организации кода с использованием systools, код каждого приложения помещается в отдельный каталог lib/Application-Vsn, где Vsn - номер версии.

Также полезно знать, что даже когда systools не используется, т.к. Erlang/OTP сам организовн с принципами OTP и таким образом организован в виде такой структуры каталогов. Код-сервер (см. code(3)) автамотически использует код из каталога с наибольшим номером версии, в том случае, если имеется более одной версии приложения.

Структура каталогов приложения, конечно, также может быть использована и в окружении разработки. В таком случае номер версии в имени может быть опущен.

Каталог приложения содержит следующие подкаталоги:

  • src
  • ebin
  • priv
  • include

src

Содержит исходный код на Erlang.

ebin

Содержит код обьектов Erlang'а, beam-файлы. .app-файл также помещается сюда.

priv

Используется для файлов, специфичных для приложения. К примеру, исполняемые файлы на C помещают сюда. Для доступа к этому каталогу следует использовать функцию code:priv_dir/1.

include

Используется для include-файлов ???.

Контроллер приложений

Когда запускается рантайм система Erlang'а, определённое число процессов запускаются как часть приложения Kernel. Одним из таких процессов является процесс контроллера приложений, зарегистрированный как application_controller.

Все операции над приложениями координируются контроллером приложений. Обращения к нему идут при помощи функций в модуле application, см. application(3). В частности, приложения могут быть загружены, выгружены, запущены и остановлены.

Загрузка и выгрузка приложений

Перед тем, как приложение сможет быть запущено, оно должно быть загружено. Контроллер приложений читает и запоминает информацию из .app-файла.

1> application:load(ch_app).
ok
2> application:loaded_applications().
[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
 {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
 {ch_app,"Channel allocator","1"}]   

Приложение, которое было остановлено или никогда не было запущено, может быть выгружено. Информации о приложении стирается из внутренней базы данных контроллера приложений.

3> application:unload(ch_app).
ok
4> application:loaded_applications().
[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
 {stdlib,"ERTS  CXC 138 10","1.11.4.3"}]   

Примечание

Загрузка/выгрузка приложения не загружает/выгружает код, используемый приложением. Загрузка кода осуществляется обычным способом.


Запуск и остановка приложений

Приложение запускается при помощи вызова:

5> application:start(ch_app).
ok
6> application:which_applications().
[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
 {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
 {ch_app,"Channel allocator","1"}]   

Если приложение ещё не загружено, контроллер приложений сначала загружает его, используя application:load/1. Он проверяет значение ключа applications, удостоверяясь, что все приложения, которые должны быть запущены до запуска приложения, выполняются.

Затем контроллер приложений создаёт "владельца" приложения для приложения. "Владелец" приложения - это групповой лидер ??? всех процессов в приложении. "Владелец" приложения запускает приложение, вызывая функцию обратного вызова приложения start/2 в его модуле, передавая параметр запуска, указанный в ключе mod в .app-файле.

Приложение останавливается, но не выгружается, при помощи вызова:

7> application:stop(ch_app).
ok   

"Владелец" приложения останавливает приложение, посылая сигнал завершения работы супервайзору высшего уровня. Супервайзор высшего уровня посылает этот сигнал всем своим дочерним процессам, и всё дерево останавливается в порядке, обратном порядку запуска. "Владелец" приложения затем вызывает функцию обратного вызова приложения stop/1 в модуле, указанном в ключе mod.

Конфигурирование приложения

Приложение может быть сконфигурировано при помощи параметров конфигурации. Они представляют собой список туплов вида {Par, Val}, указанный в ключе key env в .app-файле.

{application, ch_app,
 [{description, "Channel allocator"},
  {vsn, "1"},
  {modules, [ch_app, ch_sup, ch3]},
  {registered, [ch3]},
  {applications, [kernel, stdlib, sasl]},
  {mod, {ch_app,[]}},
  {env, [{file, "/usr/local/log"}]}
 ]}.   

Переменная Par должна быть атомом, Val - любой терм. Приложение может получить значение параметра конфигурации при помощи вызова application:get_env(App, Par) или ряда схожих функций, см. application(3).

Пример:

% erl
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
 
Eshell V5.2.3.6  (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"/usr/local/log"}
   

Значения в .app-файле могут быть перекрыты значениями в файле конфигурации системы. Этот файл содержит параметры конфигурации значимых приложений:

[{Application1, [{Par11,Val11},...]},
 ...,
 {ApplicationN, [{ParN1,ValN1},...]}].
   

Файл конфигурации системы должен иметь название Name.config и Erlang должен быть запущен с указанием аргумента коммандной строки -config Name. См. config(4) для получения более подробной информации.

Пример: Создан файл test.config следующего содержания:

[{ch_app, [{file, "testlog"}]}].
   

Значиние ключа file перекроет значение ключа file, определённое в .app-файле:

% erl -config test
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
 
Eshell V5.2.3.6  (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"testlog"}
   

Если используется работа с релизами, то только один файл конфигурации системы должен быть исопльзован, и этот файл обязан нызываться sys.config.

Значения в .app-файле, также как значния в файле конфигурации системы, могут быть перекрыты непосредственно из коммандной строки:

% erl -ApplName Par1 Val1 ... ParN ValN

Пример:

% erl -ch_app file '"testlog"'
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
Eshell V5.2.3.6  (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"testlog"}

Типы запусков приложения

Тип запуска определяется при его запуске:

application:start(Application, Type)   

Вызов application:start(Application) представляет собой то же самое, что и вызов application:start(Application, temporary). Тип может быть также permanent или transient:

  • Если приложение permanent завершается, все другие приложения и рантайм система также завершаются.
  • Если приложение transient завершается с кодом завершения normal, посылается сигнал об этом, но никакие другие приложения не завершаются. Если приложение transient завершается черезвычайно, т.е. с любым кодом завершения помимо normal, все другие приложения и рантайм система также завершаются.
  • Если завершается приложение temporary, посылается сигнал об этом, но никакие другие приложения не завершаются.

В любой момент можно явным образом завершить приложение при помощи вызова application:stop/1. Несмотря на тип запуска, никакие другие приложения не будут затронуты.

Следует заметить, что тип transient является мало полезным с практической точки зрения, т.к., когда завершается дерево надзора, код завершения имеет значение shutdown, а не normal.