Поведение супервайзора

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

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

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

Дата: 7.11.2006

Версия: 1.1


Содержание

OTP design principles

Поведение супервайзора

Эта глава должна быть прочитана вместе с supervisor(3), где даны все детали поведения супервайзора.

Принципы надзора

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

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

Пример

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

-module(ch_sup).
-behaviour(supervisor).

-export([start_link/0]).
-export([init/1]).

start_link() ->
   supervisor:start_link(ch_sup, []).

init(_Args) ->
   {ok, {{one_for_one, 1, 60},
         [{ch3, {ch3, start_link, []},
           permanent, brutal_kill, worker, [ch3]}]}}.    

one_for_one - это стратегия перезапусков.

1 и 60 определяют максимальную частоту перезапусков.

Кортеж {ch3, ...} - это спецификация потомка.

Стратегия перезапусков

one_for_one

Если завершается дочерний процесс, то только он будет перезапущен.

Стратегия One_For_One
Стратегия One_For_One
one_for_all

Если дочерний процесс завершается, то все дочерние процессы будут завершены, и затем все дочерние процессы, включая завершившийся вначале, будут перезапущены.

Стратегия One_For_All
Стратегия One_For_All
rest_for_one

Если дочерний процесс завершается, «оставшиеся» дочерние процессы – т.е. дочерние процессы, идущие после завершившегося в списке запуска процессов – будут завершены. Затем завершившийся дочерний процесс и оставшиеся перезапускаются.

Максимальная частота перезапусков

У супервайзоров существует встроенный механизм, ограничивающий число перезапусков, которые могут произойти за заданный временной интервал. Это число определяется двумя параметрами (MaxR и MaxT) в спецификации запуска, возвращаемой callback-функцией init:

init(...) ->
   {ok, {{RestartStrategy, MaxR, MaxT},
         [ChildSpec, ...]}}.    

Если больше чем MaxR перезапусков произойдёт в течение последних MaxT секунд, супервайзор завершает все дочерние процессы, а затем завершается сам. Когда завершается супервайзор, супервайзор более высокого уровня предпринимает какое-то действие. Он или перезапускает завершившегося супервайзора, или завершается сам. Смысл такого механизма перезапуска состоит в том, чтобы предотвратить ситуацию, когда процесс погибает повторно по одной и той же причине.

Спецификации потомков

Вот определение типа спецификации потомков:

{Id, StartFunc, Restart, Shutdown, Type, Modules}
   Id = term()
   StartFunc = {M, F, A}
       M = F = atom()
       A = [term()]
   Restart = permanent | transient | temporary
   Shutdown = brutal_kill | integer() >=0 | infinity
   Type = worker | supervisor
   Modules = [Module] | dynamic
       Module = atom()    
  • Поле Id – это имя, используемое для идентификации спецификации потомков внутри самого супервайзора.
  • Поле StartFunc определяет функцию, используемую для запуска дочернего процесса. Это тупл "модуль-функция-аргументы", используемый как apply(M, F, A). Эта функция должна быть вызовом supervisor:start_link, gen_server:start_link, gen_fsm:start_link или gen_event:start_link, или иметь результатом такой вызов. (Также она может быть функцией соответствующего формата, см. подробности в supervisor(3)).
  • Поле Restart определяет, когда завершившийся дочерний процесс должен быть перезапущен.
    • permanent – дочерний процесс перезапускается всегда.
    • temporary – дочерний процесс никогда не перезапускается.
    • transient – дочерний процесс перезапускается, только если он завершается черезвычайно, т.е. причиной завершения отличающейся от normal.
  • Поле Shutdown определяет, как дочерний процесс должен быть завершён.
    • brutal_kill означает, что дочерний процесс завершается безусловным вызовом exit(Child, kill).
    • Целочисленное значение таймаута обозначает, что супервайзор приказывает дочернему процессу завершиться, вызывая exit(Child, shutdown), и затем ожидает сигнала завершения в ответ. Если он не получает его в течение заданного времени, дочерний процесс завершается безусловно, используя exit(Child, kill).
    • Если дочерний процесс также является супервайзором, это поле должно быть выставлено как infinity (бесконечность), чтобы дать поддереву процессов достаточно времени для завершения.
  • Поле Type описывает, является ли дочерний процесс супервайзором или рабочим процессом.
  • Поле Modules должно содержать список из одного элемента [Module], где Module – это имя модуля обратного вызова, если дочерний процесс является супервайзором, gen_server или gen_fsm. Если дочерний процесс является gen_event, поле Modules должно быть выставлено как dynamic. Эта информация используется обработчиком релизов при апгрейдах и даунгрейдах, см. управление релизами.

Пример: Спецификация потомков для запуска сервера ch3 в примере выше выглядит следующим образом:

{ch3,
 {ch3, start_link, []},
 permanent, brutal_kill, worker, [ch3]}    


Пример: Спецификация потомков для запуска менеджера событий из главы о gen_event:

{error_man,
 {gen_event, start_link, [{local, error_man}]},
 permanent, 5000, worker, dynamic}    


И сервер, и менеджер событий – зарегистрированные процессы, которые должны быть доступны всё время, поэтому они описаны как permanent. ch3 не нуждается в какой-либо очистке ресурсов перед завершением, поэтому время таймаута не нужно, и brutal_kill подходит для него. error_man нужно некоторое время для очистки обработчиков событий, поэтому в поле Shutdown выставлено значение в 5000 миллисекунд. Пример: спецификация потомков для запуска другого супервайзора:

{sup,
 {sup, start_link, []},
 transient, infinity, supervisor, [sup]}

Запуск супервайзора

В примере, приведённом выше, супервайзор запускается вызовом ch_sup:start_link():

start_link() ->
   supervisor:start_link(ch_sup, []).


ch_sup:start_link вызывает функцию supervisor:start_link/2. Эта функция создаёт новый процесс, и создаёт связь между ним и супервайзором.

  • Первый аргумент ch_sup – имя модуля обратного вызова, т.е. модуля, где содержится функция обратного вызова init.
  • Второй аргумент [] – терм, который передаётся «as-is» в функцию обратного вызова init. Здесь init не нужно входных данных, и она игнорирует аргумент. В этом случае супервайзор не регистрируется, вместо имени нужно использовать его pid. Имя может быть указано в вызове supervisor:start_link({local, Name}, Module, Args) или supervisor:start_link({global, Name}, Module, Args). Новый процесс супервайзора вызывает функцияю обратного вызова ch_sup:init([]). От init ожидается возврат вида {ok, StartSpec}:
init(_Args) ->
   {ok, {{one_for_one, 1, 60},
         [{ch3, {ch3, start_link, []},
           permanent, brutal_kill, worker, [ch3]}]}}.


Затем супервайзор запускает все дочерние процессы в соответствии со спецификацией потомков в спецификации запуска. В этом случае имеется только один дочерний процесс ch3.

Примечание

Вызов supervisor:start_link – синхронный. Он не возвращается до того момента, пока все дочерние процессы не будут запущены.


Добавление дочерних процессов

Вдобавок к статическому дереву надзора супервайзора, мы можем также динамически добавлять дочерние процессы к существующему супервайзору при помощи следующего вызова:

supervisor:start_child(Sup, ChildSpec)    

Sup представляет собой pid, или имя супервайзора. ChildSpec – спецификация потомков. Дочерние процессы, добавленные при помощи start_child/2, ведут себя таким же образом, как и другие дочерние процессы, за минусом одного важного исключения: если супервайзор завершается и создаётся заново, все дочерние процессы, которые были динамически добавлены, будут утеряны.

Остановка дочернего процесса

Любой дочерний процесс, статический или динамический, может быть остановлен согласно спецификации завершения:

supervisor:terminate_child(Sup, Id)    

Спецификация потомков для остановленного дочернего процесса удаляется при помощи следующего вызова:

supervisor:delete_child(Sup, Id)


Sup представляет собой pid, или имя супервайзора. Id – идентификатор, указанный в спецификации потомков. Так же, как и с динамически добавленными дочерними процессами, эффект удаления статического дочернего процесса исчезает в случае перезапуска супервайзора.

Simple-One-For-One супервайзоры

Супервайзор со стратегией перезапусков simple_one_for_one – это упрощённый one_for_one супервайзор, в котором все дочерние процессы представляют собой динамически добавляемые экземпляры одного и того же процесса. Пример модуля обратного вызова для simple_one_for_one супервайзора:

-module(simple_sup).
-behaviour(supervisor).

-export([start_link/0]).
-export([init/1]).

start_link() ->
   supervisor:start_link(simple_sup, []).

init(_Args) ->
   {ok, {{simple_one_for_one, 0, 1},
         [{call, {call, start_link, []},
           temporary, brutal_kill, worker, [call]}]}}.    


При запуске супервайзор не запускает никаких дочерних процессов. Вместо этого дочерние процессы добавляются динамически при помощи:

supervisor:start_child(Sup, List)


Sup представляет собой pid или имя супервайзора. List – произвольный список термов, который будет добавлен к списку аргументов, указанных в спецификации потомков. Если функция запуска указана в виде {M, F, A}, то дочерний процесс запускается вызовом apply(M, F, A++List). К примеру, добавление потомка к simple_sup показанному выше:

supervisor:start_child(Pid, [id1])

даёт в результате дочерний процесс, запущенный вызовом apply(call, start_link, []++[id1]), или на самом деле:

call:start_link(id1)

Остановка

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