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. Существует 7 возможных обратных вызовов для реализации при использовании 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.
Регистрация имени
Как 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, «обычные» сообщения, отправленные функциями, такими как 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
@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
# In 2 hours
Process.send_after(self(), :work, 2 * 60 * 60 * 1000)
end
end Таймауты
Возвращаемое значение init/1 или любого из обратных вызовов handle_* может включать значение таймаута в миллисекундах; если нет, предполагается :infinity.
Если в процессе нет ожидающих сообщений, когда устанавливается таймаут, и заданное количество миллисекунд проходит без поступления сообщения, тогда handle_info/2 будет вызван с :timeout в качестве первого аргумента. Таймаут сбрасывается, если ожидающее сообщение или сообщение поступает до заданного таймаута.
Поскольку сообщение может поступить до установки таймаута, даже таймаут в 0 миллисекунд не гарантирует его выполнение. Чтобы немедленно и безоговорочно выполнить другую операцию, используйте инструкцию :continue.
Когда не стоит использовать GenServer
До сих пор мы узнали, что GenServer может использоваться как управляемый процесс, обрабатывающий синхронные и асинхронные вызовы. Он также может обрабатывать системные сообщения, такие как периодические сообщения и события мониторинга. Процессы GenServer также могут иметь имена.
GenServer, или любой процесс в общем случае, должен использоваться для моделирования временных характеристик вашей системы. GenServer никогда не должен использоваться для организации кода.
В Elixir организация кода выполняется с помощью модулей и функций, процессы не нужны. Например, представьте, что вы реализуете калькулятор, и решите поместить все операции калькулятора в GenServer:
def add(a, b) do
GenServer.call(__MODULE__, {:add, 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 по началу работы предоставляет учебное введение. Документация и ссылки в Erlang также могут предоставить дополнительную информацию.
Резюме
Типы
- 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, 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если процесс не ассоциирован с данным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, state)
Вызывается для обработки инструкций
continue.- handle_info(msg, state)
Вызывается для обработки всех остальных сообщений.
- init(init_arg)
Вызывается при запуске сервера.
start_link/3илиstart/3будут заблокированы до его возвращения.- terminate(reason, state)
Вызывается, когда сервер собирается выйти. Он должен выполнить все необходимые действия по очистке.
Типы
debug()
Specs
debug() :: [:trace | :log | :statistics | {:log_to_file, Path.t()}] Поддерживаемые параметры отладки для функций start*
from()
Specs
from() :: {pid(), tag :: term()} Кортеж, описывающий клиента запроса вызова.
pid — PID вызывающего лица, а tag — уникальное значение, используемое для идентификации вызова.
name()
Specs
name() :: atom() | {:global, term()} | {:via, module(), term()} Имя GenServer
on_start()
Specs
on_start() ::
{:ok, pid()} | :ignore | {:error, {:already_started, pid()} | term()} Значения, возвращаемые функциями start*
option()
Specs
option() ::
{:debug, debug()}
| {:name, name()}
| {:timeout, timeout()}
| {:spawn_opt, Process.spawn_opt()}
| {:hibernate_after, timeout()} Значения параметров, используемые функциями start*
options()
Specs
options() :: [option()]
Параметры, используемые функциями start*
server()
Specs
server() :: pid() | name() | {atom(), node()} Ссылка на сервер.
Это либо обычный PID, либо значение, представляющее зарегистрированное имя. Более подробная информация содержится в разделе "Регистрация имен" этого документа.
Функции
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 находится на узле, который еще не подключен к вызывающему процессу, семантика отличается в зависимости от используемой версии Erlang/OTP.
server может быть любым из значений, описанных в разделе «Регистрация имен» документации по данному модулю.
До Erlang/OTP 21 вызов блокировался до подключения. Это было сделано для гарантии порядка. Начиная с Erlang/OTP 21, как Erlang, так и Elixir не блокируют вызов.
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, init_arg, options \\ [])
Спецификации
start(module(), any(), options()) :: on_start()
Запускает процесс GenServer без связей (вне дерева наблюдения).
Для получения дополнительной информации см. start_link/3.
start_link(module, init_arg, options \\ [])
Спецификации
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)
Спецификации
stop(server(), reason :: term(), timeout()) :: :ok
Синхронно останавливает сервер с указанным reason.
Обратный вызов terminate/2 данного server будет вызван перед завершением. Функция возвращает :ok если сервер завершается с указанной причиной; если он завершается по другой причине, вызов завершается.
Эта функция поддерживает семантику OTP относительно отчётности об ошибках. Если причина — любая, кроме :normal, :shutdown или {:shutdown, _}, регистрируется отчёт об ошибке.
whereis(server)
Спецификации
whereis(server()) :: pid() | {atom(), node()} | nil Возвращает pid или {name, node} процесса GenServer, или nil если процесс, связанный с данным server, не найден.
Примеры
Например, для поиска процесса сервера, мониторинга его и отправки ему асинхронного сообщения:
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) при обновлении. При понижении версии предыдущая версия заключается в кортеж из двух элементов, где первый — :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 | {:continue, term()}}
| {:noreply, new_state}
| {:noreply, new_state, timeout() | :hibernate | {:continue, 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}} аналогичен {:reply, reply, new_state}, но handle_continue/2 будет вызвана немедленно после этого со значением continue в качестве первого аргумента.
Не следует злоупотреблять спящим состоянием, так как слишком много времени может быть потрачено на сборку мусора. Обычно оно используется только тогда, когда сообщение вряд ли появится в ближайшее время, и минимизация памяти процесса оказывается выгодной.
Возврат {:noreply, new_state} не отправляет ответ вызывающей стороне и продолжает цикл с новым состоянием new_state. Ответ необходимо отправить с помощью reply/2.
Существует три основных случая, когда ответ не возвращается через возвращаемое значение:
- Отправить ответ до возвращения из обратного вызова, так как ответ известен до вызова медленной функции.
- Отправить ответ после возвращения из обратного вызова, так как ответ ещё недоступен.
- Отправить ответ из другого процесса, например, задачи.
Если ответ отправляется из другого процесса, GenServer должен завершиться, если другой процесс завершится без отправки ответа, так как вызывающая сторона будет ожидать ответ.
Возврат {:noreply, new_state, timeout | :hibernate | {:continue, continue}} аналогичен {: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)
Спецификации
handle_cast(request :: term(), state :: term()) ::
{:noreply, new_state}
| {:noreply, new_state, timeout() | :hibernate | {:continue, 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}} аналогичен {:noreply, new_state}, но handle_continue/2 будет вызвана немедленно после этого со значением continue в качестве первого аргумента.
Возврат {:stop, reason, new_state} останавливает цикл и вызывает terminate/2 с причиной reason и состоянием new_state. Процесс завершается с причиной reason.
Этот обратчик необязателен. Если он не реализован, сервер потерпит неудачу, если будет выполнен вызов.
handle_continue(continue, state)
Спецификации
handle_continue(continue :: term(), state :: term()) ::
{:noreply, new_state}
| {:noreply, new_state, timeout() | :hibernate | {:continue, term()}}
| {:stop, reason :: term(), new_state}
when new_state: term() Вызывается для обработки инструкций continue.
Полезно для выполнения работы после инициализации или для разделения работы в обратном вызове на несколько этапов, обновляя состояние процесса по ходу выполнения.
Значения возврата такие же, как у handle_cast/2.
Этот обратчик необязателен. Если он не реализован, сервер потерпит неудачу, если будет использована инструкция continue.
Этот обратчик поддерживается только в Erlang/OTP 21 и выше.
handle_info(msg, state)
Спецификации
handle_info(msg :: :timeout | term(), state :: term()) ::
{:noreply, new_state}
| {:noreply, new_state, timeout() | :hibernate | {:continue, term()}}
| {:stop, reason :: term(), new_state}
when new_state: term() Вызывается для обработки всех других сообщений.
msg — сообщение, а state — текущее состояние GenServer. В случае таймаута сообщение — :timeout.
Значения возврата такие же, как у handle_cast/2.
Этот обратчик необязателен. Если он не реализован, полученное сообщение будет записано в журнал.
init(init_arg)
Характеристики
init(init_arg :: term()) ::
{:ok, state}
| {:ok, state, timeout() | :hibernate | {:continue, 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}} аналогичен {:ok, state}, за исключением того, что сразу после входа в цикл вызывается обратный вызов handle_continue/2 со значением continue в качестве первого аргумента.
Возврат :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()} Вызывается при завершении работы сервера. Он должен выполнить все необходимые действия по очистке.
reason — причина завершения, а state — текущее состояние GenServer. Значение возврата игнорируется.
terminate/2 вызывается, если обратный вызов (кроме init/1) выполняет одно из следующих действий:
- возвращает кортеж
:stop - генерирует исключение
- вызывает
Kernel.exit/1 - возвращает недопустимое значение
- процесс
GenServerобрабатывает завершения (используяProcess.flag/2) и родительский процесс отправляет сигнал завершения
Если процесс является частью дерева надзора, он получит сигнал завершения при завершении работы дерева. Сигнал завершения основан на стратегии завершения в спецификации дочернего элемента, где это значение может быть:
-
: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}, регистрируется ошибка.
Этот обратный вызов необязателен.
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.9.4/GenServer.html