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 в свой модуль, Elixir автоматически определит все 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, который использует эти функции для хранения списка имен процессов и их соответствующих идентификаторов процессов, доступных глобально для сети узлов Elixir. Elixir также поставляется с локальным, децентрализованным и масштабируемым реестром, называемымRegistry, для локального хранения динамически сгенерированных имён.
Например, мы можем запустить и зарегистрировать наш 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- атом, если сервер зарегистрирован локально
-
{atom, node}если сервер зарегистрирован локально на другом узле -
{:global, term}если сервер зарегистрирован глобально -
{:via, module, name}если сервер зарегистрирован через альтернативный реестр
Если есть необходимость регистрации динамических имён локально, не используйте атомы, так как атомы никогда не удаляются из памяти, и поэтому динамически сгенерированные атомы не будут удалены. В таких случаях можно настроить собственный локальный реестр, используя модуль Registry.
Клиентский / Серверный 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» внутри обратных вызовов GenServer, так как это приведёт к некорректной работе GenServer.
Помимо синхронной и асинхронной коммуникации, предоставляемой call/3 и cast/2, «обычные» сообщения, отправленные функциями, такими как Kernel.send/2, Process.send_after/4 и аналогичные, могут обрабатываться внутри обратного вызова handle_info/2.
handle_info/2 может использоваться во многих ситуациях, например, для обработки сообщений DOWN, отправленных Process.monitor/1. Другой сценарий использования для handle_info/2 — выполнение периодической работы с помощью Process.send_after/4:
defmodule MyApp.Periodically do
use GenServer
def start_link do
GenServer.start_link(__MODULE__, %{})
end
def init(state) do
schedule_work() # Schedule work to be performed on start
{:ok, state}
end
def handle_info(:work, state) do
# Do the desired work here
schedule_work() # Reschedule once more
{:noreply, state}
end
defp schedule_work() do
Process.send_after(self(), :work, 2 * 60 * 60 * 1000) # In 2 hours
end
end Отладка с использованием модуля :sys
GenServer, как специальные процессы, могут отлаживаться с помощью модуля :sys. С помощью различных хуков этот модуль позволяет разработчикам получить информацию о состоянии процесса и отслеживать системные события, происходящие во время его выполнения, такие как полученные сообщения, отправленные ответы и изменения состояния.
Давайте рассмотрим основные функции модуля :sys для отладки:
-
:sys.get_state/2— позволяет получить состояние процесса. В случае процесса GenServer это будет состояние модуля обратного вызова, передаваемое в функции обратного вызова как последний аргумент. -
:sys.get_status/2— позволяет получить статус процесса. Этот статус включает в себя словарь процесса, если процесс работает или приостановлен, родительский идентификатор процесса, состояние отладчика и состояние модуля поведения, которое включает состояние модуля обратного вызова (как возвращается функцией:sys.get_state/2). Можно изменить представление этого статуса, определив необязательный обратный вызовGenServer.format_status/2. -
:sys.trace/3— печатает все системные события в:stdio. -
:sys.statistics/3— управляет сбором статистики процесса. -
:sys.no_debug/2— отключает все обработчики отладки для данного процесса. Очень важно отключить отладку после завершения работы. Чрезмерное количество обработчиков отладки или тех, которые должны быть отключены, но не были, могут серьёзно повлиять на производительность системы.
Давайте посмотрим, как можно использовать эти функции для отладки сервера стека, который мы определили ранее.
iex> {:ok, pid} = Stack.start_link([])
iex> :sys.statistics(pid, true) # turn on collecting process statistics
iex> :sys.trace(pid, true) # turn on event printing
iex> Stack.push(pid, 1)
*DBG* <0.122.0> got cast {push,1}
*DBG* <0.122.0> new state [1]
:ok
iex> :sys.get_state(pid)
[1]
iex> Stack.pop(pid)
*DBG* <0.122.0> got call pop from <0.80.0>
*DBG* <0.122.0> sent 1 to <0.80.0>, new state []
1
iex> :sys.statistics(pid, :get)
{:ok,
[start_time: {{2016, 7, 16}, {12, 29, 41}},
current_time: {{2016, 7, 16}, {12, 29, 50}},
reductions: 117, messages_in: 2, messages_out: 0]}
iex> :sys.no_debug(pid) # turn off all debug handlers
:ok
iex> :sys.get_status(pid)
{:status, #PID<0.122.0>, {:module, :gen_server},
[["$initial_call": {Stack, :init, 1}, # pdict
"$ancestors": [#PID<0.80.0>, #PID<0.51.0>]],
:running, # :running | :suspended
#PID<0.80.0>, # parent
[], # debugger state
[header: 'Status for generic server <0.122.0>', # module status
data: [{'Status', :running}, {'Parent', #PID<0.80.0>},
{'Logged events', []}], data: [{'State', [1]}]]]} Узнать больше
Если вы хотите узнать больше о GenServer, руководство Elixir Getting Started предоставляет вводный урок. Документация и ссылки в Erlang также могут предоставить дополнительную информацию.
- GenServer — Руководство Elixir Getting Started
-
Документация модуля
:gen_server - Поведение gen_server — Принципы проектирования OTP
- Клиенты и серверы — Learn You Some Erlang for Great Good!
Резюме
Типы
- debug()
-
Параметры отладки, поддерживаемые функциями
start* - from()
-
Кортеж, описывающий клиента запроса вызова
- name()
-
Имя GenServer
- on_start()
-
Значения возвращаемые функциями
start* - option()
-
Значения параметров, используемых функциями
start* - options()
-
Параметры, используемые функциями
start* - server()
-
Ссылка на сервер
Функции
- abcast(nodes \\ [node() | Node.list()], name, request)
-
Распространяет все серверы, локально зарегистрированные как
name, на указанных узлах - call(server, request, timeout \\ 5000)
-
Выполняет синхронный вызов
serverи ожидает его ответа - cast(server, request)
-
Отправляет асинхронный запрос
server - multi_call(nodes \\ [node() | Node.list()], 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 — 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 \\ [node() | Node.list()], 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 \\ [node() | Node.list()], 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 — список имён узлов, на которые отправляется запрос. По умолчанию это список всех известных узлов (включая этот узел).
Для предотвращения загрязнения очереди сообщений вызывающего процесса ответами с опозданием (после таймаута), используется процесс-посредник для выполнения фактических вызовов. Поздние ответы будут затем отброшены, когда они придут в завершённый процесс.
Примеры
Предполагая, что Stack GenServer, упомянутый в документации для модуля 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)
{:noreply, state}
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 — PID сервера. Если процесс с указанным именем сервера уже существует, функция возвращает {:error, {:already_started, pid}} с 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. Например, он может использоваться для возвращения компактного представления состояния GenServer, чтобы избежать вывода больших терминов состояния.
-
Один из
: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 — 2-кортеж, содержащий PID вызывающего процесса и термин, уникально идентифицирующий вызов, а 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 входит в состав дерева управления, его 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/0, они будут закрыты при получении сигнала о завершении GenServer и не нуждаются в закрытии в terminate/2.
Если reason не :normal, :shutdown ни {:shutdown, term}, регистрируется ошибка.
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.4.5/GenServer.html