Spec-Zone.ru › Elixir 1.3

GenServer поведение

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

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

Пример

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

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

defmodule Stack do
  use GenServer

  # Callbacks

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

  def handle_cast({:push, item}, state) do
    {:noreply, [item | state]}
  end
end

# Start the server
{:ok, pid} = GenServer.start_link(Stack, [:hello])

# This is the client
GenServer.call(pid, :pop)
#=> :hello

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

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

Мы начинаем наш Stack вызовом start_link/3, передавая модуль с реализацией сервера и его начальный аргумент (список, представляющий стек, содержащий элемент :hello). Мы можем в основном взаимодействовать с сервером, отправляя два типа сообщений. Сообщения call ожидают ответа от сервера (и поэтому являются синхронными), а сообщения cast — нет.

Каждый раз, когда вы выполняете GenServer.call/3, клиент отправляет сообщение, которое должно обрабатываться обратным вызовом handle_call/3 в GenServer. Сообщение cast/2 должно обрабатываться handle_cast/2.

Обратные вызовы

Для реализации в GenServer необходимо реализовать 6 обратных вызовов. Добавление use GenServer в ваш модуль автоматически определит все 6 обратных вызовов для вас, оставив вам возможность реализовать те, которые вы хотите настроить.

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

И start_link/3, и start/3 поддерживают GenServer для регистрации имени при запуске через опцию :name. Зарегистрированные имена также автоматически удаляются при завершении работы. Поддерживаемые значения:

  • атом — GenServer регистрируется локально с заданным именем с помощью Process.register/2.

  • {:global, term} — GenServer регистрируется глобально с заданным термином с помощью функций в модуле :global.

  • {:via, module, term} — GenServer регистрируется с заданным механизмом и именем. Опция :via ожидает модуль, который экспортирует register_name/2, unregister_name/1, whereis_name/1 и send/2. Одним из таких примеров является модуль :global, который использует эти функции для поддержания списка имен процессов и их соответствующих идентификаторов PID, доступных глобально для сети узлов Erlang.

Например, мы могли бы запустить и зарегистрировать наш сервер Stack локально следующим образом:

# Start the server and register it locally with name MyStack
{:ok, _} = GenServer.start_link(Stack, [:hello], name: MyStack)

# Now messages can be sent directly to MyStack
GenServer.call(MyStack, :pop) #=> :hello

После запуска сервера оставшиеся функции в этом модуле (call/3, cast/2 и другие) также будут принимать атом или любые :global или :via кортежи. В целом поддерживаются следующие форматы:

  • а pid
  • атом, если сервер зарегистрирован локально
  • кортеж, если сервер зарегистрирован локально на другом узле
  • кортеж, если сервер зарегистрирован глобально
  • кортеж, если сервер зарегистрирован через альтернативную систему регистрации

Клиентский/Серверный API

Хотя в приведенном выше примере мы использовали GenServer.start_link/3 и другие функции для непосредственного запуска и взаимодействия с сервером, в большинстве случаев мы не вызываем функции GenServer напрямую. Вместо этого мы оборачиваем вызовы в новые функции, представляющие общедоступный API сервера.

Вот лучшее реализация нашего модуля Stack:

defmodule Stack do
  use GenServer

  # Client

  def start_link(default) do
    GenServer.start_link(__MODULE__, default)
  end

  def push(pid, item) do
    GenServer.cast(pid, {:push, item})
  end

  def pop(pid) do
    GenServer.call(pid, :pop)
  end

  # Server (callbacks)

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

  def handle_call(request, from, state) do
    # Call the default implementation from GenServer
    super(request, from, state)
  end

  def handle_cast({:push, item}, state) do
    {:noreply, [item | state]}
  end

  def handle_cast(request, state) do
    super(request, state)
  end
end

На практике часто используются и функции сервера, и функции клиента в одном модуле. Если реализация сервера и/или клиента становится сложной, вы можете разделить их по разным модулям.

Прием пользовательских сообщений

Цель GenServer состоит в том, чтобы абстрагировать цикл «receive» для разработчиков, автоматически обрабатывая системные сообщения, поддерживая изменения кода, синхронные вызовы и многое другое. Поэтому вы никогда не должны вызывать собственный «receive» внутри обратных вызовов GenServer, так как это приведет к неправильной работе GenServer. Если вы хотите получать пользовательские сообщения, всегда получайте их в handle_info/2.

Дополнительная информация

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

  • GenServer – Руководство Elixir по началу работы
  • Документация модуля :gen_server
  • Поведение gen_server – Принципы проектирования OTP
  • Клиенты и серверы – Учите Erlang!

Сводка

Типы

debug()

Опции отладки, поддерживаемые функциями start*

from()

Кортеж, описывающий клиента запроса вызова

name()

Имя GenServer

on_start()

Значения возврата функций start*

option()

Значения опций, используемые функциями start*

options()

Опции, используемые функциями start*

server()

Ссылка на сервер

Функции

abcast(nodes \\ nodes(), name, request)

Отправляет сообщение всем серверам, зарегистрированным локально как name на указанных узлах

call(server, request, timeout \\ 5000)

Выполняет синхронный вызов на server и ожидает его ответа

cast(server, request)

Отправляет асинхронный запрос на server

multi_call(nodes \\ nodes(), name, request, timeout \\ :infinity)

Вызывает все серверы, зарегистрированные локально как name на указанных nodes

reply(client, reply)

Отправляет ответ клиенту

start(module, args, options \\ [])

Запускает процесс GenServer без ссылок (вне дерева управления)

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

Запускает процесс GenServer со ссылкой на текущий процесс

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

Останавливает сервер с указанным reason

whereis(pid)

Возвращает pid или {name, node} процесса GenServer или nil, если процесс не связан с заданным именем

Обратные вызовы

code_change(old_vsn, state, extra)

Вызывается для изменения состояния GenServer при загрузке другой версии модуля (горячая замена кода) и структура состояния должна быть изменена

format_status(reason, pdict_and_state)

Вызывается в некоторых случаях для получения отформатированной версии состояния GenServer

handle_call(request, from, state)

Вызывается для обработки синхронных сообщений call/3. call/3 будет ожидать ответа (если вызов не истечет или узлы не будут отключены)

handle_cast(request, state)

Вызывается для обработки асинхронных сообщений cast/2

handle_info(msg, state)

Вызывается для обработки всех других сообщений

init(args)

Вызывается при запуске сервера. start_link/3 (или start/3) будет ожидать возврата

terminate(reason, state)

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

Типы

debug()

debug() :: [:trace | :log | :statistics | {:log_to_file, Path.t}]

Опции отладки, поддерживаемые функциями start*

from()

from() :: {pid, tag :: term}

Кортеж, описывающий клиента запроса.

pid — это идентификатор процесса вызывающего процесса, а tag — уникальный термин, используемый для идентификации вызова.

name()

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

Имя GenServer

on_start()

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

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

option()

option ::
  {:debug, debug} |
  {:name, name} |
  {:timeout, timeout} |
  {:spawn_opt, Process.spawn_opt}

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

options()

options() :: [option]

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

server()

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

Ссылка на сервер

Функции

abcast(nodes \\ nodes(), name, request)

abcast([node], name :: atom, term) :: :abcast

Рассылает сообщение всем локально зарегистрированным серверам как name на указанных узлах.

Эта функция возвращает значение немедленно и игнорирует узлы, которые не существуют, или где имя сервера не существует.

См. multi_call/4 для получения дополнительной информации.

call(server, request, timeout \\ 5000)

call(server, term, timeout) :: term

Выполняет синхронный вызов серверу server и ожидает его ответа.

Клиент отправляет заданный request серверу и ждет, пока не придет ответ или не истечет время ожидания. На сервере будет вызвана функция handle_call/3 для обработки запроса.

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

Таймауты

timeout — это целое число, большее нуля, которое указывает, сколько миллисекунд ждать ответа, или атом :infinity для неограниченного ожидания. Значение по умолчанию — 5000. Если ответ не получен в течение указанного времени, вызов функции завершается ошибкой, и вызывающий процесс завершается. Если вызывающий процесс перехватывает ошибку и продолжает выполнение, а сервер просто опоздал с ответом, он может прийти в любой момент позже в очередь сообщений вызывающего процесса. В этом случае вызывающий процесс должен быть готов к этому и отбрасывать любые такие сообщения-мусор, которые являются кортежами из двух элементов, где первым элементом является ссылка.

cast(server, request)

cast(server, term) :: :ok

Отправляет асинхронный запрос серверу server.

Эта функция всегда возвращает :ok независимо от того, существует ли целевой server (или узел). Поэтому неизвестно, успешно ли целевой server обработала сообщение.

Функция handle_cast/2 будет вызвана на сервере для обработки запроса. Если server находится на узле, который еще не подключен к вызывающему процессу, вызов будет заблокирован до тех пор, пока подключение не произойдет. Это отличается от поведения в OTP’s :gen_server, где сообщение отправляется другой процесс в этом случае, что может привести к тому, что сообщения другим узлам придут в неправильном порядке.

multi_call(nodes \\ nodes(), name, request, timeout \\ :infinity)

multi_call([node], name :: atom, term, timeout) :: {replies :: [{node, term}], bad_nodes :: [node]}

Вызывает все локально зарегистрированные серверы как name на указанных nodes.

Сначала request отправляется на каждый узел в nodes, затем вызывающий процесс ожидает ответов. Эта функция возвращает кортеж из двух элементов {replies, bad_nodes}, где:

  • replies — список кортежей {node, reply}, где node — узел, который ответил, а reply — его ответ
  • bad_nodes — список узлов, на которых либо не существовал узел, либо сервер с данным name не существовал или не ответил

nodes — это список имён узлов, которым отправляется запрос. Значение по умолчанию — список всех известных узлов (включая этот узел).

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

Примеры

Предположим, что GenServer Stack, упомянутый в документации по модулю GenServer, зарегистрирован как Stack на узлах :"foo@my-machine" и :"bar@my-machine".

GenServer.multi_call(Stack, :pop)
#=> {[{:"foo@my-machine", :hello}, {:"bar@my-machine", :world}], []}

reply(client, reply)

reply(from, term) :: :ok

Отвечает клиенту.

Эта функция может использоваться для явного отправки ответа клиенту, который вызвал call/3 или multi_call/4, когда ответ не может быть указан в возвращаемом значении handle_call/3.

client должно быть значением аргумента from (второй аргумент), принятым обратными вызовами handle_call/3. reply — произвольный термин, который будет возвращен клиенту в качестве возвращаемого значения вызова.

Обратите внимание, что reply/2 может быть вызван из любого процесса, а не только GenServer, который изначально получил вызов (при условии, что этот GenServer каким-то образом передал аргумент from).

Эта функция всегда возвращает :ok.

Примеры

def handle_call(:reply_in_one_second, from, state) do
  Process.send_after(self(), {:reply, from}, 1_000)
  {:noreply, state}
end

def handle_info({:reply, from}, state) do
  GenServer.reply(from, :one_second_has_passed)
end

start(module, args, options \\ [])

start(module, any, options) :: on_start

Запускает процесс GenServer без связей (вне дерева управления).

См. start_link/3 для получения дополнительной информации.

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

start_link(module, any, options) :: on_start

Запускает процесс GenServer, связанный с текущим процессом.

Это часто используется для запуска GenServer в качестве части дерева управления.

После запуска сервера функция init/1 заданного module вызывается с args в качестве аргументов для инициализации сервера. Чтобы обеспечить синхронизированную процедуру запуска, эта функция не возвращает значение, пока init/1 не вернёт значение.

Обратите внимание, что GenServer, запущенный с помощью start_link/3, связан с родительским процессом и завершит работу в случае сбоя родительского процесса. GenServer также завершит работу по причинам :normal, если он настроен на перехват завершений в обратном вызове init/1.

Параметры

  • :name — используется для регистрации имени, как описано в разделе «Регистрация имен» документации по модулю.

  • :timeout — если присутствует, серверу разрешено потратить указанное количество миллисекунд на инициализацию, иначе он будет завершён, а функция запуска вернёт {:error, :timeout}

  • :debug — если присутствует, соответствующая функция в модуле :sys вызывается.

  • :spawn_opt — если присутствует, его значение передаётся в качестве параметров в подлежащий процесс, как в Process.spawn/4

Возвращаемые значения

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

Если обратный вызов init/1 завершается с ошибкой reason, эта функция возвращает {:error, reason}. В противном случае, если он возвращает {:stop, reason} или :ignore, процесс завершается, и эта функция возвращает {:error, reason} или :ignore, соответственно.

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

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

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

Обратный вызов terminate/2 заданного server будет вызван перед завершением. Функция возвращает :ok если сервер завершится с заданной причиной; если он завершится по другой причине, вызов завершится.

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

whereis(pid)

whereis(server) :: pid | {atom, node} | nil

Возвращает pid или {name, node} процесса GenServer, или nil, если процессу не соответствует данное имя.

Примеры

Например, чтобы найти процесс сервера, отслеживать его и отправить ему сообщение:

process = GenServer.whereis(server)
monitor = Process.monitor(process)
GenServer.cast(process, :hello)

Обратные вызовы

code_change(old_vsn, state, extra)

code_change(old_vsn, state :: term, extra :: term) ::
  {:ok, new_state :: term} |
  {:error, reason :: term} when old_vsn: term | {:down, term}

Вызывается для изменения состояния GenServer при загрузке другой версии модуля (горячая замена кода), и структура данных состояния должна быть изменена.

old_vsn — предыдущая версия модуля (определённая атрибутом @vsn) при обновлении. При понижении версии предыдущая версия упаковывается в кортеж из 2 элементов, где первый элемент — :down. state — текущее состояние GenServer, а extra — любые дополнительные данные, необходимые для изменения состояния.

Возврат {:ok, new_state} изменяет состояние на new_state, и изменение кода выполняется успешно.

Возврат {:error, reason} приводит к ошибке изменения кода с причиной reason, и состояние остаётся прежним.

Если code_change/3 вызывает ошибку, изменение кода завершается неудачей, и цикл продолжит выполнение в предыдущем состоянии. Поэтому этот обратный вызов обычно не содержит побочных эффектов.

format_status(reason, pdict_and_state) (необязательно)

format_status(reason, pdict_and_state :: list) :: term when reason: :normal | :terminate

Вызывается в некоторых случаях для получения отформатированной версии статуса GenServer.

Этот обратный вызов может быть полезен для управления отображением состояния GenServer. Например, он может использоваться для возвращения компактного представления состояния GenServers для избежания вывода длинных терминов состояния.

  • Один из :sys.get_status/1 или :sys.get_status/2 вызывается для получения статуса GenServer; в таких случаях, reason является :normal

  • GenServer завершается с ошибкой и записывает ошибку в журнал; в таких случаях, reason является :terminate

pdict_and_state — это список из двух элементов [pdict, state], где pdict — список кортежей {key, value}, представляющих текущий словарь процесса GenServer, а state — текущее состояние GenServer.

handle_call(request, from, state)

handle_call(request :: term, from, state :: term) ::
  {:reply, reply, new_state} |
  {:reply, reply, new_state, timeout | :hibernate} |
  {:noreply, new_state} |
  {:noreply, new_state, timeout | :hibernate} |
  {:stop, reason, reply, new_state} |
  {:stop, reason, new_state} when reply: term, new_state: term, reason: term

Вызывается для обработки синхронных сообщений call/3. call/3 будет блокироваться до получения ответа (если вызов не истечет или узлы не будут отключены).

request — это сообщение запроса, отправленное клиентом call/3, from — кортеж из двух элементов, содержащий идентификатор процесса отправителя и терм, уникально идентифицирующий вызов, и state — текущее состояние GenServer.

Возврат {:reply, reply, new_state} отправляет ответ reply отправителю и продолжает цикл с новым состоянием new_state.

Возврат {:reply, reply, new_state, timeout} аналогичен {:reply, reply, new_state} за исключением того, что handle_info(:timeout, new_state) будет вызван через timeout миллисекунд, если сообщения не получены.

Возврат {:reply, reply, new_state, :hibernate} аналогичен {:reply, reply, new_state} за исключением того, что процесс будет приостановлен и продолжит цикл, когда сообщение появится в очереди сообщений. Если сообщение уже находится в очереди сообщений, это произойдёт немедленно. Приостановка GenServer вызывает сборку мусора и сохраняет непрерывную кучу, что сводит к минимуму память, используемую процессом.

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

Возврат {:noreply, new_state} не отправляет ответ отправителю и продолжает цикл с новым состоянием new_state. Ответ должен быть отправлен с помощью reply/2.

Существует три основных случая, когда не следует отвечать, используя возвращаемое значение:

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

При ответе от другого процесса GenServer должен завершиться, если другой процесс завершится без ответа, поскольку отправитель будет блокироваться в ожидании ответа.

Возврат {:noreply, new_state, timeout | :hibernate} аналогичен {:noreply, new_state} за исключением того, что происходит таймаут или приостановка, как и с кортежем :reply.

Возврат {:stop, reason, reply, new_state} останавливает цикл, и terminate/2 вызывается с причиной reason и состоянием new_state. Затем reply отправляется как ответ на вызов, и процесс завершается с причиной reason.

Возврат {:stop, reason, new_state} аналогичен {:stop, reason, reply, new_state} за исключением того, что ответ не отправляется.

Если этот обратный вызов не реализован, по умолчанию реализация use GenServer вернёт {:stop, {:bad_call, request}, state}.

handle_cast(request, state)

handle_cast(request :: term, state :: term) ::
  {:noreply, new_state} |
  {:noreply, new_state, timeout | :hibernate} |
  {:stop, reason :: term, new_state} when new_state: term

Вызывается для обработки асинхронных сообщений cast/2.

request — это сообщение запроса, отправленное клиентом cast/2, а state — текущее состояние GenServer.

Возврат {:noreply, new_state} продолжает цикл с новым состоянием new_state.

Возврат {:noreply, new_state, timeout} аналогичен {:noreply, new_state} за исключением того, что handle_info(:timeout, new_state) будет вызван через timeout миллисекунд, если сообщения не получены.

Возврат {:noreply, new_state, :hibernate} аналогичен {:noreply, new_state} за исключением того, что процесс приостанавливается перед продолжением цикла. Подробнее см. handle_call/3.

Возврат {:stop, reason, new_state} останавливает цикл, и terminate/2 вызывается с причиной reason и состоянием new_state. Процесс завершается с причиной reason.

Если этот обратный вызов не реализован, по умолчанию реализация use GenServer вернёт {:stop, {:bad_cast, request}, state}.

handle_info(msg, state)

handle_info(msg :: :timeout | term, state :: term) ::
  {:noreply, new_state} |
  {:noreply, new_state, timeout | :hibernate} |
  {:stop, reason :: term, new_state} when new_state: term

Вызывается для обработки всех остальных сообщений.

msg — это сообщение, а state — текущее состояние GenServer. При возникновении таймаута сообщение является :timeout.

Возвращаемые значения такие же, как в handle_cast/2.

Если этот обратный вызов не реализован, по умолчанию реализация use GenServer вернёт {:noreply, state}.

init(args)

init(args :: term) ::
  {:ok, state} |
  {:ok, state, timeout | :hibernate} |
  :ignore |
  {:stop, reason :: any} when state: any

Вызывается при запуске сервера. start_link/3 (или start/3) будет блокироваться до возврата.

args — терм аргумента (второй аргумент), переданный в start_link/3.

Возврат {:ok, state} приведет к тому, что start_link/3 вернёт {:ok, pid}, и процесс перейдёт в цикл.

Возврат {:ok, state, timeout} аналогичен {:ok, state} за исключением того, что handle_info(:timeout, state) будет вызван через timeout миллисекунд, если в течение этого времени сообщения не будут получены.

Возврат {:ok, state, :hibernate} аналогичен {:ok, state} за исключением того, что процесс приостанавливается перед входом в цикл. Подробнее см. handle_call/3.

Возврат :ignore приведет к тому, что start_link/3 вернёт :ignore и процесс завершится нормально без входа в цикл или вызова terminate/2. Если используется в дереве надзора, родительский надзиратель не потерпит неудачи при запуске и не попытается немедленно перезапустить GenServer. Остальная часть дерева надзора будет (пере)запущена, поэтому GenServer не должен требоваться другим процессам. Его можно запустить позже с помощью Supervisor.restart_child/2, так как описание дочернего элемента сохраняется в родительском надзирателе. Основные случаи использования:

  • GenServer отключен настройками, но может быть включен позже.
  • Произошла ошибка, и она будет обработана другим механизмом, чем Supervisor. Вероятно, этот подход предполагает вызов Supervisor.restart_child/2 с задержкой для попытки перезапуска.

Возврат {:stop, reason} приведет к тому, что start_link/3 вернёт {:error, reason} и процесс завершится с причиной reason без входа в цикл или вызова terminate/2.

terminate(reason, state)

terminate(reason, state :: term) :: term when reason: :normal | :shutdown | {:shutdown, term} | term

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

reason — причина выхода, а state — текущее состояние GenServer. Возвращаемое значение игнорируется.

terminate/2 вызывается, если обратный вызов (кроме init/1) возвращает кортеж :stop, вызывает исключение, вызывает Kernel.exit/1 или возвращает недопустимое значение. Он также может быть вызван, если GenServer перехватывает выходы с использованием Process.flag/2 и родительский процесс отправляет сигнал выхода.

Если сервер входит в состав дерева надзора, GenServer‘s Supervisor отправит сигнал выхода при его остановке. Сигнал выхода основан на стратегии завершения в описании дочернего элемента. Если это :brutal_kill, GenServer уничтожается, и поэтому terminate/2 не вызывается. Однако, если это таймаут, Supervisor отправит сигнал выхода :shutdown, и у GenServer будет время таймаута для вызова terminate/2. Если процесс всё ещё жив после истечения таймаута, он уничтожается.

Если GenServer получает сигнал выхода (который не :normal) от любого процесса, когда он не перехватывает выходы, он завершится внезапно с той же причиной, и поэтому не вызовет terminate/2. Обратите внимание, что процесс НЕ перехватывает выходы по умолчанию, и сигнал выхода отправляется, когда связанный процесс завершается или его узел отключен.

Поэтому нет гарантии, что terminate/2 вызывается при выходе GenServer. По этим причинам мы обычно рекомендуем важные правила очистки, выполняемые в отдельных процессах, либо с помощью мониторинга, либо с помощью самих связей. Например, если GenServer управляет port (например, :gen_tcp.socket) или File.io_device, они будут закрыты при получении сигнала выхода GenServer и не потребуют закрытия в terminate/2.

Если reason не :normal, :shutdown или {:shutdown, term}, регистрируется ошибка.

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

Spec-Zone.ru

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