GenServer поведение
Модуль поведения для реализации сервера в клиент-серверном взаимодействии.
GenServer — это процесс, как и любой другой процесс Elixir, и он может использоваться для хранения состояния, выполнения кода асинхронно и так далее. Преимущество использования общего серверного процесса (GenServer), реализованного с помощью этого модуля, заключается в том, что у него будет стандартный набор функций интерфейса и функции отслеживания и обработки ошибок. Он также будет подходить для использования в дереве надзора.
Пример
Поведение GenServer абстрагирует общее взаимодействие клиент-сервер. Разработчикам требуется только реализовать вызовы и функциональность, которые их интересуют.
Давайте начнем с примера кода и затем рассмотрим доступные вызовы. Представим, что мы хотим реализовать сервис с GenServer, который работает как стек, позволяя нам добавлять и извлекать элементы. Мы настроим общий GenServer с собственным модулем, реализовав три вызова.
init/1 преобразует наш начальный аргумент в начальное состояние для GenServer. handle_call/3 срабатывает, когда сервер получает синхронное pop сообщение, извлекая элемент из стека и возвращая его пользователю. handle_cast/2 будет срабатывать, когда сервер получит асинхронное push сообщение, помещая элемент в стек:
defmodule Stack do
use GenServer
# Callbacks
@impl true
def init(elements) do
initial_state = String.split(elements, ",", trim: true)
{:ok, initial_state}
end
@impl true
def handle_call(:pop, _from, state) do
[to_caller | new_state] = state
{:reply, to_caller, new_state}
end
@impl true
def handle_cast({:push, element}, state) do
new_state = [element | state]
{:noreply, new_state}
end
end
Мы оставляем механизм работы процесса, передачу сообщений и цикл обработки сообщений поведению GenServer и сосредотачиваемся только на реализации стека. Теперь мы можем использовать API GenServer для взаимодействия с сервисом, создавая процесс и отправляя ему сообщения:
# Start the server
{:ok, pid} = GenServer.start_link(Stack, "hello,world")
# This is the client
GenServer.call(pid, :pop)
#=> "hello"
GenServer.cast(pid, {:push, "elixir"})
#=> :ok
GenServer.call(pid, :pop)
#=> "elixir"
Мы запускаем наш Stack, вызывая start_link/2, передавая модуль с реализацией сервера и его начальный аргумент со списком элементов через запятую. Поведение GenServer вызывает вызов init/1 для создания начального состояния GenServer. С этого момента GenServer имеет контроль, поэтому мы взаимодействуем с ним, отправляя два типа сообщений на клиенте. Сообщения call ожидают ответ от сервера (и поэтому являются синхронными), а сообщения cast — нет.
Каждый вызов GenServer.call/3 приводит к сообщению, которое должно быть обработано вызовом handle_call/3 в GenServer. Сообщение cast/2 должно быть обработано вызовом handle_cast/2. GenServer поддерживает 8 вызовов, но требуется только init/1.
use GenServerПри вызове
use GenServer, модульGenServerустановит@behaviour GenServerи определит функциюchild_spec/1, так что ваш модуль может использоваться в качестве дочернего элемента в дереве надзора.
Клиентский/серверный API
Хотя в примере выше мы использовали GenServer.start_link/3 и аналогичные функции для непосредственного запуска и общения с сервером, в большинстве случаев мы не вызываем функции GenServer напрямую. Вместо этого мы оборачиваем вызовы в новые функции, представляющие общедоступный API сервера. Эти тонкие оболочки называются клиентским API.
Вот лучшая реализация модуля Stack:
defmodule Stack do
use GenServer
# Client
def start_link(default) when is_binary(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(elements) do
initial_state = String.split(elements, ",", trim: true)
{:ok, initial_state}
end
@impl true
def handle_call(:pop, _from, state) do
[to_caller | new_state] = state
{:reply, to_caller, new_state}
end
@impl true
def handle_cast({:push, element}, state) do
new_state = [element | state]
{:noreply, new_state}
end
end
На практике часто используются как серверные, так и клиентские функции в одном модуле. Если реализация сервера и/или клиента становится сложной, вы можете поместить их в разные модули.
Как контролировать
GenServer чаще всего запускается в дереве надзора. При вызове use GenServer, он автоматически определяет функцию child_spec/1, которая позволяет запустить Stack напрямую под надзирателем. Чтобы запустить стандартный стек ["hello", "world"] под надзирателем, можно сделать следующее:
children = [
{Stack, "hello,world"}
]
Supervisor.start_link(children, strategy: :one_for_all)
Обратите внимание, что указание модуля MyServer эквивалентно указанию кортежа {MyServer, []}.
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]}]
]
]}
Узнайте больше
Если вы хотите узнать больше о GenServer, руководство 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.
Этот обратный вызов является необязательным. Если он не реализован, сервер не сможет обрабатывать сообщения cast.
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. Однако не гарантируется, что terminate/2 вызывается при завершении работы GenServer. Поэтому важную очистку следует выполнять с помощью связей процессов и/или мониторинга. Мониторинг процесса получит тот же код выхода reason, который был бы передан в terminate/2.
terminate/2 вызывается, если:
процесс
GenServerловит выходы (с использованиемProcess.flag/2) и родительский процесс (тот, который вызвалstart_link/1) посылает сигнал выхода-
обратная функция (кроме
init/1) выполняет одно из следующего:
Если процесс является частью дерева надзора, GenServer получит сигнал выхода от своего родительского процесса (супервайзера) при завершении работы дерева. Сигнал выхода основан на стратегии завершения в спецификации дочернего процесса, где это значение может быть:
:brutal_kill:GenServerубивается, иterminate/2не вызывается.значение таймаута, где супервайзер пошлёт сигнал выхода
:shutdown, и уGenServerбудет время таймаута для завершения. Если после истечения срока таймаута процесс по-прежнему жив, он будет немедленно убит.
Для более подробного объяснения см. раздел «Значения завершения (:shutdown)» в модуле Supervisor.
Если GenServer получает сигнал выхода (не :normal) от любого процесса, когда он не ловит выходы, он завершится внезапно с той же причиной, не вызывая terminate/2. Обратите внимание, что процесс не ловит выходы по умолчанию, и сигнал выхода отправляется при завершении связанного процесса или отключении узла.
terminate/2 вызывается только после того, как GenServer обработает все сообщения, которые поступили в его почтовый ящик до сигнала выхода. Если он получит сигнал :kill до обработки этих сообщений, terminate/2 не будет вызван. Если terminate/2 вызывается, любые сообщения, полученные после сигнала выхода, останутся в почтовом ящике.
Нет необходимости в очистке, когда 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.15.4/GenServer.html