GenServer поведение
Модуль поведения для реализации сервера в клиент-серверной связи.
GenServer — это процесс, подобный любому другому процессу Elixir, и он может использоваться для хранения состояния, выполнения кода асинхронно и так далее. Преимущество использования процесса-сервера общего назначения (GenServer), реализованного с помощью этого модуля, заключается в том, что он будет иметь стандартный набор интерфейсных функций и включать функциональность для отслеживания и обработки ошибок. Он также будет соответствовать структуре надзора.
Пример
Поведение GenServer абстрагирует общие взаимодействия клиент-сервер. Разработчикам требуется только реализовать вызовы и функциональность, которые их интересуют.
Начнем с примера кода, а затем рассмотрим доступные вызовы. Представим, что нам нужен GenServer, который работает как стек, позволяя нам помещать и извлекать элементы:
defmodule Stack do
use GenServer
# Callbacks
@impl true
def init(stack) do
{:ok, stack}
end
@impl true
def handle_call(:pop, _from, [head | tail]) do
{:reply, head, tail}
end
@impl true
def handle_cast({:push, element}, state) do
{:noreply, [element | 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/2, передавая модуль с реализацией сервера и его начальное аргумент (список, представляющий стек, содержащий элемент :hello). Мы можем взаимодействовать с сервером, в основном, отправляя два типа сообщений. Сообщения call ожидают ответа от сервера (и, следовательно, являются синхронными), в то время как сообщения cast не ожидают ответа.
Каждый раз, когда вы выполняете GenServer.call/3, клиент отправит сообщение, которое должно обрабатываться вызовом handle_call/3 в GenServer. Сообщение cast/2 должно обрабатываться вызовом handle_cast/2. Существует 8 возможных вызовов, которые нужно реализовать при использовании GenServer. Единственный обязательный вызов — init/1.
Клиент/Сервер API
Хотя в примере выше мы использовали GenServer.start_link/3 и аналогичные функции для непосредственного запуска и взаимодействия с сервером, в большинстве случаев мы не вызываем функции GenServer напрямую. Вместо этого мы оборачиваем вызовы в новые функции, представляющие общедоступный API сервера.
Вот улучшенная реализация нашего модуля Stack:
defmodule Stack do
use GenServer
# Client
def start_link(default) when is_list(default) do
GenServer.start_link(__MODULE__, default)
end
def push(pid, element) do
GenServer.cast(pid, {:push, element})
end
def pop(pid) do
GenServer.call(pid, :pop)
end
# Server (callbacks)
@impl true
def init(stack) do
{:ok, stack}
end
@impl true
def handle_call(:pop, _from, [head | tail]) do
{:reply, head, tail}
end
@impl true
def handle_cast({:push, element}, state) do
{:noreply, [element | state]}
end
end
На практике часто используются как функции сервера, так и клиента в одном модуле. Если реализации сервера и/или клиента становятся сложными, вы можете разместить их в разных модулях.
Как настроить надзор
GenServer чаще всего запускается в структуре надзора. Когда мы вызываем use GenServer, он автоматически определяет функцию child_spec/1, которая позволяет запустить Stack непосредственно под супервайзером. Чтобы запустить стандартный стек [:hello] под супервайзером, можно сделать следующее:
children = [
{Stack, [:hello]}
]
Supervisor.start_link(children, strategy: :one_for_all)
Обратите внимание, что вы также можете запустить его просто как Stack, что эквивалентно {Stack, []}.
children = [
Stack # The same as {Stack, []}
]
Supervisor.start_link(children, strategy: :one_for_all)
В обоих случаях Stack.start_link/1 всегда вызывается.
use GenServer также принимает список опций, который настраивает описание дочернего процесса и, следовательно, то, как он работает под супервайзером. Сгенерированную функцию child_spec/1 можно настроить с помощью следующих опций:
-
:id- идентификатор описания дочернего процесса, по умолчанию — текущий модуль -
:restart- когда дочерний процесс должен быть перезапущен, по умолчанию —:permanent -
:shutdown- как остановить дочерний процесс, либо немедленно, либо дав ему время на завершение
Например:
use GenServer, restart: :transient, shutdown: 10_000
Дополнительную информацию см. в разделе «Описание дочернего процесса» в модуле Supervisor. Аннотация @doc сразу перед use GenServer будет добавлена к сгенерированной функции child_spec/1.
При остановке GenServer, например, путем возвращения кортежа {:stop, reason, new_state} из вызова, причина выхода используется супервайзером для определения, нужно ли перезапускать GenServer. См. раздел «Причины выхода и перезапуски» в модуле Supervisor.
Регистрация имени
Как 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, которые доступны глобально для сети узлов 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.
Получение «обычных» сообщений
Цель GenServer — абстрагировать цикл «получить» для разработчиков, автоматически обрабатывать системные сообщения, поддерживать изменения кода, синхронные вызовы и многое другое. Поэтому вы никогда не должны вызывать свой собственный «получить» внутри вызовов GenServer, так как это приведет к некорректной работе GenServer.
Помимо синхронной и асинхронной коммуникации, предоставляемой call/3 и cast/2, «обычные» сообщения, отправленные функциями, такими как 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
@impl true
def init(state) do
# Schedule work to be performed on start
schedule_work()
{:ok, state}
end
@impl true
def handle_info(:work, state) do
# Do the desired work here
# ...
# Reschedule once more
schedule_work()
{:noreply, state}
end
defp schedule_work do
# We schedule the work to happen in 2 hours (written in milliseconds).
# Alternatively, one might write :timer.hours(2)
Process.send_after(self(), :work, 2 * 60 * 60 * 1000)
end
end
Таймауты
Возвращаемое значение init/1 или любого из вызовов handle_* может включать значение таймаута в миллисекундах; если нет, используется значение :infinity. Таймаут может использоваться для обнаружения перерывов в поступлении сообщений.
Значение timeout() используется следующим образом:
Если в процессе уже есть ожидающее сообщение, когда возвращается значение
timeout(), таймаут игнорируется, и ожидающее сообщение обрабатывается как обычно. Это означает, что даже таймаут в0миллисекунд не гарантируется к выполнению (если вы хотите немедленно и безусловно выполнить другое действие, используйте инструкцию:continue).Если любое сообщение придет до истечения указанного количества миллисекунд, таймаут сбрасывается, и это сообщение обрабатывается как обычно.
В противном случае, когда указанное количество миллисекунд истекло, а сообщение не пришло, вызывается
handle_info/2с:timeoutв качестве первого аргумента.
Когда не стоит использовать GenServer
До сих пор мы узнали, что GenServer может использоваться как контролируемый процесс, обрабатывающий синхронные и асинхронные вызовы. Он также может обрабатывать системные сообщения, такие как периодические сообщения и события мониторинга. Процессы GenServer также могут иметь имена.
GenServer, или процесс вообще, должен использоваться для моделирования характеристик выполнения вашей системы. GenServer никогда не должен использоваться для организации кода.
В Elixir организация кода выполняется с помощью модулей и функций, процессы не нужны. Например, представьте, что вы реализуете калькулятор, и решите поместить все операции калькулятора за GenServer:
def add(a, b) do
GenServer.call(__MODULE__, {:add, a, b})
end
def subtract(a, b) do
GenServer.call(__MODULE__, {:subtract, a, b})
end
def handle_call({:add, a, b}, _from, state) do
{:reply, a + b, state}
end
def handle_call({:subtract, a, b}, _from, state) do
{:reply, a - b, state}
end
Это антипаттерн не только потому, что он усложняет логику калькулятора, но и потому, что вы помещаете логику калькулятора в один процесс, который потенциально может стать узким местом в вашей системе, особенно по мере увеличения количества вызовов. Вместо этого просто определите функции непосредственно:
def add(a, b) do a + b end def subtract(a, b) do a - b end
Если вам не нужен процесс, то вам и не нужен процесс. Используйте процессы только для моделирования свойств выполнения, таких как изменяемое состояние, параллельность и сбои, а не для организации кода.
Отладка с помощью модуля :sys
GenServer, как специальные процессы, могут быть отлажены с помощью модуля :sys. С помощью различных хуков этот модуль позволяет разработчикам исследовать состояние процесса и отслеживать системные события, происходящие во время его выполнения, такие как полученные сообщения, отправленные ответы и изменения состояния.
Рассмотрим основные функции из модуля :sys, используемые для отладки:
-
:sys.get_state/2— позволяет получить состояние процесса. В случае процесса GenServer, это будет состояние модуля обратного вызова, переданное в функции обратного вызова в качестве последнего аргумента. -
:sys.get_status/2— позволяет получить статус процесса. Этот статус включает словарь процесса, если процесс работает или приостановлен, родительский PID, состояние отладчика и состояние модуля поведения, которое включает состояние модуля обратного вызова (как возвращается:sys.get_state/2). Можно изменить представление этого статуса, определив необязательный обратный вызовGenServer.format_status/2. -
:sys.trace/3— выводит все системные события в:stdio. -
:sys.statistics/3— управляет сбором статистики процессов. -
:sys.no_debug/2— отключает все обработчики отладки для данного процесса. Очень важно отключить отладку после завершения работы. Чрезмерное количество обработчиков отладки или тех, которые должны быть отключены, но не были, могут серьёзно навредить производительности системы. -
:sys.suspend/2— позволяет приостановить процесс, чтобы он отвечал только на системные сообщения, но не на другие. Приостановленный процесс можно активировать с помощью:sys.resume/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}, # process dictionary
"$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]}]
]
]}
Узнать больше
Если вы хотите узнать больше о GenServers, руководство Elixir Getting Started предоставляет вводный учебник. Документация и ссылки в Erlang также могут предоставить дополнительную информацию.
Краткое описание
Типы
- debug()
Поддерживаемые параметры отладки для функций
start*- from()
Кортеж, описывающий клиента запроса.
- name()
Имя GenServer
- on_start()
Возвращаемые значения функций
start*- option()
Значения параметров, используемые функциями
start*- options()
Параметры, используемые функциями
start*- server()
Ссылка на сервер.
Обратные вызовы
- 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_continue(continue_arg, state)
Вызывается для обработки инструкций продолжения.
- handle_info(msg, state)
Вызывается для обработки всех остальных сообщений.
- init(init_arg)
Вызывается при запуске сервера.
start_link/3илиstart/3будет ожидать его возвращения.- terminate(reason, state)
Вызывается, когда сервер собирается завершить работу. Он должен выполнить все необходимые действия по очистке.
Функции
- 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, init_arg, options \\ [])
Запускает процесс
GenServerбез связей (вне дерева управления).- start_link(module, init_arg, options \\ [])
Запускает процесс
GenServerсо связью с текущим процессом.- stop(server, reason \\ :normal, timeout \\ :infinity)
Синхронно останавливает сервер с указанным
reason.- whereis(server)
Возвращает
pidили{name, node}процесса GenServer,nilв противном случае.
Типы
debug()Source
@type debug() :: [:trace | :log | :statistics | {:log_to_file, Path.t()}] Параметры отладки, поддерживаемые функциями start*
from()Source
@type from() :: {pid(), tag :: term()} Кортеж, описывающий клиента запроса.
pid — PID вызывающей стороны, а tag — уникальный термин, используемый для идентификации запроса.
name()Source
@type name() :: atom() | {:global, term()} | {:via, module(), term()} Имя GenServer
on_start()Source
@type on_start() ::
{:ok, pid()} | :ignore | {:error, {:already_started, pid()} | term()} Возвращаемые значения функций start*
option()Source
@type option() ::
{:debug, debug()}
| {:name, name()}
| {:timeout, timeout()}
| {:spawn_opt, [Process.spawn_opt()]}
| {:hibernate_after, timeout()} Значения опций, используемые функциями start*
options()Source
@type options() :: [option()]
Опции, используемые функциями start*
server()Source
@type server() :: pid() | name() | {atom(), node()} Ссылка на сервер.
Это либо обычный PID, либо значение, представляющее зарегистрированное имя. Подробнее см. раздел "Регистрация имен" в этом документе.
Обработчики событий
code_change(old_vsn, state, extra)Source
@callback 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) при обновлении. При обратной совместимости предыдущая версия обернута в кортеж из двух элементов, первым из которых является :down. state — текущее состояние GenServer, а extra — любые дополнительные данные, необходимые для изменения состояния.
Возврат {:ok, new_state} изменяет состояние на new_state и изменение кода выполняется успешно.
Возврат {:error, reason} приводит к ошибке изменения кода с причиной reason, и состояние остаётся прежним.
Если code_change/3 вызывает исключение, изменение кода завершается неудачно, и цикл продолжит выполняться с предыдущим состоянием. Поэтому в этом обработчике обычно отсутствуют побочные эффекты.
Этот обработчик является необязательным.
format_status(reason, pdict_and_state)Source
@callback format_status(reason, pdict_and_state :: list()) :: term() when reason: :normal | :terminate
Вызывается в некоторых случаях для получения отформатированной версии состояния GenServer:
при вызове одной из функций
:sys.get_status/1или:sys.get_status/2для получения статусаGenServer; в таких случаях,reasonравно:normalпри аварийном завершении
GenServerи записи ошибки в журнал; в таких случаях,reasonравно:terminate
Этот обработчик может быть полезен для управления отображением статуса GenServer. Например, он может использоваться для возврата компактного представления состояния GenServer для предотвращения вывода больших структур данных состояния.
pdict_and_state — это список из двух элементов [pdict, state], где pdict — список кортежей {key, value}, представляющих текущий словарь процесса GenServer, а state — текущее состояние GenServer.
handle_call(request, from, state)Source
@callback handle_call(request :: term(), from(), state :: term()) ::
{:reply, reply, new_state}
| {:reply, reply, new_state,
timeout() | :hibernate | {:continue, continue_arg :: term()}}
| {:noreply, new_state}
| {:noreply, new_state,
timeout() | :hibernate | {:continue, continue_arg :: term()}}
| {: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 — кортеж из двух элементов, содержащий PID вызывающего и уникальный идентификатор запроса, а state — текущее состояние GenServer.
Возврат {:reply, reply, new_state} отправляет ответ reply вызывающему и продолжает цикл с новым состоянием new_state.
Возврат {:reply, reply, new_state, timeout} аналогичен {:reply, reply, new_state}, за исключением того, что он также устанавливает тайм-аут. Дополнительную информацию см. в разделе «Тайм-ауты» в документации модуля.
Возврат {:reply, reply, new_state, :hibernate} аналогичен {:reply, reply, new_state}, но процесс переводится в спящий режим, и продолжит цикл, когда сообщение окажется в очереди сообщений. Однако, если сообщение уже находится в очереди сообщений, процесс продолжит цикл немедленно. Перевод GenServer в спящий режим вызывает сборку мусора и оставляет непрерывную кучу, что сводит к минимуму используемую память процессом.
Возврат {:reply, reply, new_state, {:continue, continue_arg}} аналогичен {:reply, reply, new_state}, за исключением того, что handle_continue/2 будет вызван сразу же после этого с continue_arg в качестве первого аргумента и state в качестве второго.
Не следует чрезмерно использовать перевод в спящий режим, так как может быть затрачено слишком много времени на сборку мусора. Обычно это следует делать только тогда, когда сообщение в ближайшее время не ожидается и уменьшение используемой памяти процессом оказывается полезным.
Возврат {:noreply, new_state} не отправляет ответ вызывающему и продолжает цикл с новым состоянием new_state. Ответ должен быть отправлен с помощью reply/2.
Существует три основных случая, когда не нужно отвечать с помощью возвращаемого значения:
- Для отправки ответа до возвращения из обработчика, потому что ответ известен до вызова медленной функции.
- Для отправки ответа после возвращения из обработчика, потому что ответ ещё не доступен.
- Для отправки ответа из другого процесса, например, задачи.
При отправке ответа из другого процесса GenServer должен завершиться, если другой процесс завершится без отправки ответа, так как вызывающий процесс будет блокироваться, ожидая ответа.
Возврат {:noreply, new_state, timeout | :hibernate | {:continue, continue_arg}} аналогичен {: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}, за исключением того, что ответ не отправляется.
Этот обработчик является необязательным. Если он не реализован, сервер завершится с ошибкой, если будет выполнен вызов.
handle_cast(request, state)Source
@callback handle_cast(request :: term(), state :: term()) ::
{:noreply, new_state}
| {:noreply, new_state,
timeout() | :hibernate | {:continue, continue_arg :: term()}}
| {: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}, но также устанавливает тайм-аут. Дополнительную информацию см. в разделе «Тайм-ауты» в документации модуля.
Возврат {:noreply, new_state, :hibernate} аналогичен {:noreply, new_state}, но процесс переводится в спящий режим перед продолжением цикла. Дополнительную информацию см. в handle_call/3.
Возврат {:noreply, new_state, {:continue, continue_arg}} аналогичен {:noreply, new_state}, за исключением того, что handle_continue/2 будет вызван сразу же после этого с continue_arg в качестве первого аргумента и state в качестве второго.
Возврат {:stop, reason, new_state} останавливает цикл и вызывает terminate/2 с причиной reason и состоянием new_state. Процесс завершается с причиной reason.
Этот обработчик является необязательным. Если он не реализован, сервер завершится с ошибкой, если будет выполнен запрос.
handle_continue(continue_arg, state)Source
@callback handle_continue(continue_arg, state :: term()) ::
{:noreply, new_state}
| {:noreply, new_state, timeout() | :hibernate | {:continue, continue_arg}}
| {:stop, reason :: term(), new_state}
when new_state: term(), continue_arg: term() Вызывается для обработки инструкций продолжения.
Это полезно для выполнения работы после инициализации или для разделения работы в обработчике на несколько этапов, обновляя состояние процесса по ходу выполнения.
Возвращаемые значения аналогичны handle_cast/2.
Этот обработчик является необязательным. Если он не реализован, сервер завершится с ошибкой, если будет использована инструкция продолжения.
handle_info(msg, state)Source
@callback handle_info(msg :: :timeout | term(), state :: term()) ::
{:noreply, new_state}
| {:noreply, new_state,
timeout() | :hibernate | {:continue, continue_arg :: term()}}
| {:stop, reason :: term(), new_state}
when new_state: term() Вызывается для обработки всех других сообщений.
msg — это сообщение, а state — текущее состояние GenServer. При возникновении тайм-аута сообщение равно :timeout.
Возвращаемые значения аналогичны handle_cast/2.
Этот обработчик является необязательным. Если он не реализован, полученное сообщение будет записано в журнал.
init(init_arg)Source
@callback init(init_arg :: term()) ::
{:ok, state}
| {:ok, state, timeout() | :hibernate | {:continue, continue_arg :: term()}}
| :ignore
| {:stop, reason :: any()}
when state: any() Вызывается при запуске сервера. start_link/3 или start/3 будут блокироваться до возврата.
init_arg — это аргумент (второй аргумент), переданный в start_link/3.
Возврат значения {:ok, state} заставит start_link/3 вернуть {:ok, pid} и процесс перейти к своему циклу.
Возврат значения {:ok, state, timeout} аналогичен {:ok, state}, за исключением того, что также устанавливается таймаут. Для получения дополнительной информации см. раздел «Таймауты» в документации модуля.
Возврат значения {:ok, state, :hibernate} аналогичен {:ok, state}, за исключением того, что процесс переходит в состояние приостановки перед входом в цикл. Для получения дополнительной информации о приостановке см. handle_call/3.
Возврат значения {:ok, state, {:continue, continue_arg}} аналогичен {:ok, state}, за исключением того, что сразу после входа в цикл вызывается обратный вызов handle_continue/2 с continue_arg в качестве первого аргумента и state в качестве второго.
Возврат значения :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)Source
@callback terminate(reason, state :: term()) :: term()
when reason: :normal | :shutdown | {:shutdown, term()} | term() Вызывается перед завершением работы сервера. Должен выполнить необходимые действия по очистке.
reason — причина выхода, а state — текущее состояние GenServer. Значение возврата игнорируется.
terminate/2 вызывается, если GenServer перехватывает завершения (с использованием Process.flag/2) и родительский процесс отправляет сигнал выхода, или обратный вызов (кроме init/1) выполняет одно из следующих действий:
- возвращает кортеж
:stop - выбрасывает исключение (через
raise/2) или завершается (черезexit/1) - возвращает недопустимое значение
Если процесс является частью дерева надзора, GenServer получит сигнал выхода при завершении работы дерева. Сигнал выхода основан на стратегии завершения в спецификации дочернего элемента, где это значение может быть:
:brutal_kill:GenServerубивается, и поэтомуterminate/2не вызывается.значение таймаута, где надзорщик отправит сигнал выхода
:shutdown, и уGenServerбудет время таймаута для завершения. Если после истечения срока таймаута процесс по-прежнему жив, он будет немедленно убит.
Для более подробного объяснения см. раздел «Значения завершения (:shutdown)» в модуле Supervisor.
Если GenServer получает сигнал выхода (который не является :normal) от любого процесса, когда он не перехватывает завершения, он завершается внезапно с той же причиной и не вызывает terminate/2. Обратите внимание, что процесс НЕ перехватывает завершения по умолчанию, и сигнал выхода отправляется, когда связанный процесс завершается или его узел отключён.
Поэтому нет гарантии, что terminate/2 вызывается, когда GenServer завершается. По этим причинам мы обычно рекомендуем важные правила очистки в отдельных процессах, либо с помощью мониторинга, либо с помощью самих связей. Очистка не требуется, когда GenServer управляет port (например, :gen_tcp.socket) или File.io_device/0, так как эти объекты будут закрыты при получении сигнала выхода GenServer и не требуют ручного закрытия в terminate/2.
Если reason не является ни :normal, ни :shutdown, ни {:shutdown, term}, регистрируется ошибка.
Этот обратный вызов является необязательным.
Функции
abcast(nodes \\ [node() | Node.list()], name, request)Source
@spec abcast([node()], name :: atom(), term()) :: :abcast
Рассылает сообщение всем локально зарегистрированным серверам name на указанных узлах.
Функция возвращает значение немедленно и игнорирует узлы, которые не существуют, или где имя сервера не найдено.
См. multi_call/4 для получения дополнительной информации.
call(server, request, timeout \\ 5000)Source
@spec call(server(), term(), timeout()) :: term()
Выполняет синхронный вызов server и ожидает его ответа.
Клиент отправляет заданный request серверу и ожидает получения ответа или истечения времени ожидания. На сервере будет вызван метод handle_call/3 для обработки запроса.
server может принимать любые значения, описанные в разделе «Регистрация имен» в документации для данного модуля.
Таймауты
timeout — это целое число, большее нуля, которое указывает, сколько миллисекунд ожидать ответ, или атом :infinity для ожидания неопределённого времени. Значение по умолчанию — 5000. Если ответ не получен в течение указанного времени, вызов функции завершается ошибкой, и вызывающий процесс завершается. Если вызывающий процесс перехватывает ошибку и продолжает выполняться, а сервер просто запаздывает с ответом, он может прийти в любой момент позже в очередь сообщений вызывающего процесса. В этом случае вызывающий процесс должен быть готов к этому и отбросить любые такие мусорные сообщения, которые являются кортежами из двух элементов с ссылкой в качестве первого элемента.
cast(server, request)Source
@spec cast(server(), term()) :: :ok
Отправляет асинхронный запрос server.
Функция всегда возвращает :ok независимо от того, существует ли целевой server (или узел). Поэтому неизвестно, успешно ли целевой server обработала сообщение.
server может принимать любые значения, описанные в разделе «Регистрация имен» в документации для данного модуля.
multi_call(nodes \\ [node() | Node.list()], name, request, timeout \\ :infinity)Source
@spec 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)Source
@spec 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, init_arg, options \\ [])Source
@spec start(module(), any(), options()) :: on_start()
Запускает процесс GenServer без связей (вне дерева надзора).
См. start_link/3 для получения дополнительной информации.
start_link(module, init_arg, options \\ [])Source
@spec start_link(module(), any(), options()) :: on_start()
Запускает процесс GenServer, связанный с текущим процессом.
Это часто используется для запуска GenServer в рамках дерева надзора.
После запуска сервера вызывается функция init/1 заданного module с init_arg в качестве аргумента для инициализации сервера. Чтобы обеспечить синхронизированную процедуру запуска, эта функция не возвращает значение, пока init/1 не вернёт.
Обратите внимание, что GenServer, запущенный с помощью start_link/3, связан с родительским процессом и завершится в случае сбоя родительского процесса. GenServer также завершится по причинам :normal в случае, если он настроен на перехват выхода в обратном вызове init/1.
Параметры
:name- используется для регистрации имени, как описано в разделе «Регистрация имен» в документации дляGenServer:timeout- если присутствует, серверу разрешается потратить заданное количество миллисекунд на инициализацию, в противном случае он будет завершен, а функция запуска вернёт{:error, :timeout}:debug- если присутствует, соответствующая функция в модуле:sysвызывается:spawn_opt- если присутствует, его значение передаётся в качестве параметров в подлежащий процесс, как вProcess.spawn/4:hibernate_after- если присутствует, процесс GenServer ожидает любого сообщения в течение заданного количества миллисекунд, и если сообщение не получено, процесс переходит в состояние приостановки автоматически (вызывая:proc_lib.hibernate/3).
Значения возврата
Если сервер успешно создан и инициализирован, эта функция возвращает {: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)Source
@spec stop(server(), reason :: term(), timeout()) :: :ok
Синхронно останавливает сервер с заданной reason.
Обратный вызов terminate/2 заданного server будет вызван перед завершением. Функция возвращает :ok если сервер завершается с заданной причиной; если он завершается по другой причине, вызов завершается ошибкой.
Эта функция сохраняет семантику OTP в отношении отчётов об ошибках. Если причина — любая, кроме :normal, :shutdown или {:shutdown, _}, в журнале регистрируется сообщение об ошибке.
whereis(сервер)Исходный код
@spec whereis(server()) :: pid() | {atom(), node()} | nil Возвращает pid или {name, node} процесса GenServer, nil в противном случае.
Точнее, nil возвращается всякий раз, когда pid или {name, node} не могут быть возвращены. Обратите внимание, что нет гарантии, что возвращенный pid или {name, node} жив, так как процесс может завершиться сразу после того, как он будет найден.
Примеры
Например, чтобы найти процесс сервера, отслеживать его и отправлять в него сообщение:
process = GenServer.whereis(server) monitor = Process.monitor(process) GenServer.cast(process, :hello)
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.14.1/GenServer.html