Spec-Zone.ru › Elixir 1.4

Надзиратель поведение

Модуль поведения для реализации функциональности надзора.

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

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

Примеры

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

defmodule Stack do
  use GenServer

  def start_link(state, opts \\ []) do
    GenServer.start_link(__MODULE__, state, opts)
  end

  def handle_call(:pop, _from, [h | t]) do
    {:reply, h, t}
  end

  def handle_cast({:push, h}, t) do
    {:noreply, [h | t]}
  end
end

Теперь мы можем определить нашего надзирателя и запустить его следующим образом:

# Import helpers for defining supervisors
import Supervisor.Spec

# Supervise the Stack server which will be started with
# two arguments. The initial stack, [:hello], and a
# keyword list containing the GenServer options that
# set the registered name of the server to MyStack.
children = [
  worker(Stack, [[:hello], [name: MyStack]])
]

# Start the supervisor with our child
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

# There is one child worker started
Supervisor.count_children(pid)
#=> %{active: 1, specs: 1, supervisors: 0, workers: 1}

Обратите внимание, что при запуске GenServer мы регистрируем его под именем MyStack, что позволяет нам напрямую вызывать его и получить то, что находится в стеке:

GenServer.call(MyStack, :pop)
#=> :hello

GenServer.cast(MyStack, {:push, :world})
#=> :ok

GenServer.call(MyStack, :pop)
#=> :world

Однако в нашем сервере стека есть ошибка. Если мы вызовем :pop и стек пуст, он аварийно завершится, потому что ни одна ветка не соответствует:

GenServer.call(MyStack, :pop)
** (exit) exited in: GenServer.call(MyStack, :pop, 5000)

К счастью, поскольку сервер контролируется надзирателем, надзиратель автоматически запустит новый, со начальным стеком [:hello]:

GenServer.call(MyStack, :pop)
#=> :hello

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

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

Модульные надзиратели

В приведенном выше примере надзиратель запускался путем передачи структуры надзора в start_link/2. Однако надзиратели также могут быть созданы путем явного определения модуля надзора:

defmodule MyApp.Supervisor do
  # Automatically imports Supervisor.Spec
  use Supervisor

  def start_link do
    Supervisor.start_link(__MODULE__, [])
  end

  def init([]) do
    children = [
      worker(Stack, [[:hello]])
    ]

    # supervise/2 is imported from Supervisor.Spec
    supervise(children, strategy: :one_for_one)
  end
end

Вы можете использовать модульный надзиратель, если:

  • Вам нужно выполнить какое-либо конкретное действие при инициализации надзирателя, например, настроить таблицу ETS.

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

Стратегии

Надзиратели поддерживают различные стратегии надзора (через параметр :strategy, как показано выше):

  • :one_for_one - если дочерний процесс завершается, перезапускается только этот процесс.

  • :one_for_all - если дочерний процесс завершается, все остальные дочерние процессы завершаются, а затем все дочерние процессы (включая завершившийся) перезапускаются.

  • :rest_for_one - если дочерний процесс завершается, «остальные» дочерние процессы, т. е. дочерние процессы после завершившегося в порядке запуска, завершаются. Затем завершившийся дочерний процесс и остальные дочерние процессы перезапускаются.

  • :simple_one_for_one - аналогично :one_for_one, но лучше подходит при динамическом присоединении дочерних элементов. Эта стратегия требует, чтобы спецификация надзирателя содержала только один дочерний элемент. Многие функции в этом модуле ведут себя немного по-другому, когда используется эта стратегия.

Простой один к одному

Надзиратель :simple_one_for_one полезен, когда вы хотите динамически запускать и останавливать контролируемые дочерние элементы. Например, представьте, что вы хотите динамически создавать несколько стеков. Мы можем сделать это, определив надзирателя :simple_one_for_one:

# Import helpers for defining supervisors
import Supervisor.Spec

# This time, we don't pass any argument because
# the argument will be given when we start the child
children = [
  worker(Stack, [], restart: :transient)
]

# Start the supervisor with our one child as a template
{:ok, sup_pid} = Supervisor.start_link(children, strategy: :simple_one_for_one)

# No child worker is active yet until start_child is called
Supervisor.count_children(sup_pid)
#=> %{active: 0, specs: 1, supervisors: 0, workers: 0}

Есть несколько различий:

  • спецификация «простой один к одному» может определять только один дочерний элемент, который работает в качестве шаблона при вызове start_child/2

  • мы определили, что дочерний элемент имеет стратегию перезапуска :transient. Это означает, что если дочерний процесс завершится по причине :normal, :shutdown или {:shutdown, term}, он не будет перезапущен. Это полезно, так как позволяет нашим рабочим процессам вежливо завершаться и удаляться из надзирателя :simple_one_for_one, без перезапуска. Более подробную информацию о стратегиях перезапуска вы можете найти в документации модуля Supervisor.Spec

После определения надзирателя давайте динамически запускать стеки:

{:ok, pid} = Supervisor.start_child(sup_pid, [[:hello, :world], []])
GenServer.call(pid, :pop) #=> :hello
GenServer.call(pid, :pop) #=> :world

{:ok, pid} = Supervisor.start_child(sup_pid, [[:something, :else], []])
GenServer.call(pid, :pop) #=> :something
GenServer.call(pid, :pop) #=> :else

Supervisor.count_children(sup_pid)
#=> %{active: 2, specs: 1, supervisors: 0, workers: 2}

Причины завершения

Из приведенного выше примера вы могли заметить, что стратегия перезапуска :transient для рабочего процесса не перезапускает дочерний элемент в случае его завершения с причиной :normal, :shutdown или {:shutdown, term}.

Итак, можно задаться вопросом: какую причину завершения я должен выбрать при завершении своего рабочего процесса? Есть три варианта:

  • :normal - в таких случаях завершение не будет регистрироваться, нет перезапуска в транзитном режиме, и связанные процессы не завершаются

  • :shutdown или {:shutdown, term} - в таких случаях завершение не будет регистрироваться, нет перезапуска в транзитном режиме, и связанные процессы завершаются с той же причиной, если только они не отслеживают завершения

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

Регистрация имен

Надзиратель подчиняется тем же правилам регистрации имен, что и GenServer. Узнайте больше об этих правилах в документации по GenServer.

Резюме

Типы

child()
name()

Имя надзирателя

on_start()

Возвращаемые значения функций start_link

on_start_child()

Возвращаемые значения функций start_child

options()

Параметры, используемые функциями start*

supervisor()

Ссылка на надзирателя

Функции

count_children(supervisor)

Возвращает карту, содержащую значения подсчета для данного надзирателя

delete_child(supervisor, child_id)

Удаляет спецификацию дочернего элемента, идентифицированную по child_id

restart_child(supervisor, child_id)

Перезапускает дочерний процесс, идентифицированный по child_id

start_child(supervisor, child_spec_or_args)

Динамически добавляет спецификацию дочернего элемента к supervisor и запускает этот дочерний элемент

start_link(children, options)

Запускает надзирателя с заданными дочерними элементами

start_link(module, arg, options \\ [])

Запускает процесс надзирателя с заданным module и arg

stop(supervisor, reason \\ :normal, timeout \\ :infinity)

Останавливает данный надзиратель с указанной reason

terminate_child(supervisor, pid_or_child_id)

Завершает указанные дочерние элементы, идентифицированные по PID или ID дочернего элемента

which_children(supervisor)

Возвращает список с информацией обо всех дочерних элементах данного надзирателя

Обработчики

init(args)

Обработчик, вызываемый для запуска надзирателя и во время апгрейда кода

Типы

child()

child() :: pid() | :undefined

name()

name() :: atom() | {:global, term()} | {:via, module(), term()}

Имя надзирателя

on_start()

on_start() ::
  {:ok, pid()} |
  :ignore |
  {:error, {:already_started, pid()} | {:shutdown, term()} | term()}

Возвращаемые значения функций start_link

on_start_child()

on_start_child() ::
  {:ok, child()} |
  {:ok, child(), info :: term()} |
  {:error, {:already_started, child()} | :already_present | term()}

Возвращаемые значения функций start_child

options()

options() :: [name: name(), strategy: Supervisor.Spec.strategy(), max_restarts: non_neg_integer(), max_seconds: non_neg_integer()]

Параметры, используемые функциями start*

supervisor()

supervisor() :: pid() | name() | {atom(), node()}

Ссылка на надзирателя

Функции

count_children(supervisor)

count_children(supervisor()) :: %{specs: non_neg_integer(), active: non_neg_integer(), supervisors: non_neg_integer(), workers: non_neg_integer()}

Возвращает карту, содержащую значения подсчета для данного надзирателя.

Карта содержит следующие ключи:

  • :specs - общий подсчет дочерних элементов, мертвых или живых

  • :active - количество активно работающих дочерних процессов, управляемых этим надзирателем

  • :supervisors - количество всех надзирателей, независимо от того, живы ли эти дочерние надзиратели

  • :workers - количество всех рабочих процессов, независимо от того, живы ли эти дочерние рабочие процессы

delete_child(supervisor, child_id)

delete_child(supervisor(), Supervisor.Spec.child_id()) ::
  :ok |
  {:error, error} when error: :not_found | :simple_one_for_one | :running | :restarting

Удаляет спецификацию дочернего элемента, идентифицированную по child_id.

Соответствующий дочерний процесс не должен выполняться; используйте terminate_child/2 для его завершения, если он выполняется.

При успешном выполнении эта функция возвращает :ok. Эта функция может вернуть ошибку с соответствующей кортежем ошибки, если child_id не найден, или если текущий процесс выполняется или перезапускается.

Эта операция не поддерживается :simple_one_for_one диспетчерами.

restart_child(supervisor, child_id)

restart_child(supervisor(), Supervisor.Spec.child_id()) ::
  {:ok, child()} |
  {:ok, child(), term()} |
  {:error, error} when error: :not_found | :simple_one_for_one | :running | :restarting | term()

Перезапускает дочерний процесс, идентифицированный по child_id.

Спецификация дочернего процесса должна существовать, и соответствующий дочерний процесс не должен выполняться.

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

Если функция запуска дочернего процесса возвращает {:ok, child} или {:ok, child, info}, PID добавляется в диспетчер, и эта функция возвращает то же значение.

Если функция запуска дочернего процесса возвращает :ignore, PID остается установленным в :undefined, и эта функция возвращает {:ok, :undefined}.

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

Если функция запуска дочернего процесса возвращает кортеж ошибки или ошибочное значение, или если она завершается неудачей, эта функция возвращает {:error, error}.

Эта операция не поддерживается :simple_one_for_one диспетчерами.

start_child(supervisor, child_spec_or_args)

start_child(supervisor(), Supervisor.Spec.spec() | [term()]) :: on_start_child()

Динамически добавляет спецификацию дочернего процесса к supervisor и запускает этот дочерний процесс.

child_spec должен быть действительной спецификацией дочернего процесса (если диспетчер не является :simple_one_for_one диспетчером, см. ниже). Дочерний процесс будет запущен в соответствии с определением в спецификации дочернего процесса.

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

Если спецификация дочернего процесса с указанным идентификатором уже существует, child_spec отбрасывается, и эта функция возвращает ошибку с :already_started или :already_present соответственно, если соответствующий дочерний процесс выполняется или нет.

Если функция запуска дочернего процесса возвращает {:ok, child} или {:ok, child, info}, то спецификация дочернего процесса и PID добавляются в диспетчер, и эта функция возвращает то же значение.

Если функция запуска дочернего процесса возвращает :ignore, спецификация дочернего процесса добавляется в диспетчер, PID устанавливается в :undefined, и эта функция возвращает {:ok, :undefined}.

Если функция запуска дочернего процесса возвращает кортеж ошибки или ошибочное значение, или если она завершается неудачей, спецификация дочернего процесса отбрасывается, и функция возвращает {:error, error}, где error — термин, содержащий информацию об ошибке и спецификации дочернего процесса.

start_link(children, options)

start_link([Supervisor.Spec.spec()], options()) :: on_start()
start_link(module(), term()) :: on_start()

Запускает диспетчер с указанными дочерними процессами.

Стратегия должна быть предоставлена через опцию :strategy. Кроме того, опции :max_restarts и :max_seconds могут быть настроены, как описано в документации для Supervisor.Spec.supervise/2.

Опции также могут быть использованы для регистрации имени диспетчера. Поддерживаемые значения описаны в разделе «Регистрация имени» в документации модуля GenServer.

Если диспетчер и его дочерние процессы успешно созданы (то есть, если функция запуска каждого дочернего процесса возвращает {:ok, child}, {:ok, child, info}, или :ignore) эта функция возвращает {:ok, pid}, где pid — PID диспетчера. Если процесс с указанным именем уже существует, функция возвращает {:error, {:already_started, pid}}, где pid — PID этого процесса.

Если функция запуска любого из дочерних процессов завершается неудачей или возвращает кортеж ошибки или ошибочное значение, диспетчер сначала завершает все уже запущенные дочерние процессы с причиной :shutdown, а затем завершает себя и возвращает {:error, {:shutdown, reason}}.

Обратите внимание, что диспетчер, запущенный с помощью этой функции, связан с родительским процессом и завершается не только при сбоях, но и если родительский процесс завершается с причиной :normal.

start_link(module, arg, options \\ [])

start_link(module(), term(), options()) :: on_start()

Запускает процесс диспетчера с заданным module и arg.

Для запуска диспетчера вызывается обратный вызов init/1 в заданном module, с arg в качестве аргумента. Обратный вызов init/1 должен вернуть спецификацию диспетчера, которую можно создать с помощью функций в модуле Supervisor.Spec (особенно Supervisor.Spec.supervise/2).

Если обратный вызов init/1 возвращает :ignore, эта функция также возвращает :ignore, и диспетчер завершается с причиной :normal. Если он завершается неудачей или возвращает неправильное значение, эта функция возвращает {:error, term} , где term содержит информацию об ошибке, а диспетчер завершается с причиной term.

Опция :name также может быть задана для регистрации имени диспетчера, поддерживаемые значения описаны в разделе «Регистрация имени» в документации модуля GenServer.

stop(supervisor, reason \\ :normal, timeout \\ :infinity)

stop(supervisor(), reason :: term(), timeout()) :: :ok

Останавливает данный диспетчер с указанной reason.

Возвращает :ok , если диспетчер завершается с указанной причиной. Если он завершается по другой причине, вызов завершается.

Эта функция сохраняет семантику OTP в отношении сообщений об ошибках. Если причина отличается от :normal, :shutdown или {:shutdown, _}, регистрируется сообщение об ошибке.

terminate_child(supervisor, pid_or_child_id)

terminate_child(supervisor(), pid() | Supervisor.Spec.child_id()) ::
  :ok |
  {:error, error} when error: :not_found | :simple_one_for_one

Завершает указанные дочерние процессы, идентифицированные по PID или идентификатору дочернего процесса.

Если диспетчер не является :simple_one_for_one, ожидается идентификатор дочернего процесса, и процесс, если он существует, завершается; спецификация дочернего процесса сохраняется, если только дочерний процесс не временный.

В случае :simple_one_for_one диспетчера ожидается PID. Если вместо pid указан идентификатор спецификации дочернего процесса, функция возвращает {:error, :simple_one_for_one}.

Невременной дочерний процесс может быть позже перезапущен диспетчером. Дочерний процесс также можно перезапустить явно, вызвав restart_child/2. Используйте delete_child/2 для удаления спецификации дочернего процесса.

При успешном выполнении функция возвращает :ok. Если нет спецификации дочернего процесса для данного идентификатора или нет процесса с заданным PID, функция возвращает {:error, :not_found}.

which_children(supervisor)

which_children(supervisor()) :: [{Supervisor.Spec.child_id() | :undefined, child() | :restarting, Supervisor.Spec.worker(), Supervisor.Spec.modules()}]

Возвращает список с информацией обо всех дочерних процессах данного диспетчера.

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

Функция возвращает список кортежей {id, child, type, modules}, где:

  • id - как определено в спецификации дочернего процесса или :undefined в случае simple_one_for_one диспетчера

  • child - PID соответствующего дочернего процесса, :restarting если процесс собирается перезапуститься, или :undefined если такого процесса нет

  • type - :worker или :supervisor, как указано в спецификации дочернего процесса

  • modules - как указано в спецификации дочернего процесса

Callbacks

init(args)

init(args :: term()) ::
  {:ok, {:supervisor.sup_flags(), [Supervisor.Spec.spec()]}} |
  :ignore

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

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.4.5/Supervisor.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API