Контролируемый GenServer не перезапускается?
Я уменьшил размер вопроса, потому что он слишком большой. Здесь код:
defmodule MayRaiseGenServer do
use GenServer
def start_link do
IO.puts "started MyServer, name is #{__MODULE__}"
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def maybe_will_raise do
GenServer.call(__MODULE__, :maybe_will_raise)
end
def handle_call(:maybe_will_raise,_from, state) do
IO.puts "maybe_will_raise called!"
:random.seed(:erlang.now)
number = Enum.to_list(1..100) |> Enum.shuffle |> List.first
IO.puts "number is #{number}"
if rem(number,2) != 0 do
raise "#{number}"
end
{:reply, {"You got lucky"}, state}
end
end
defmodule MayRaiseSupervisor do
use Supervisor
def start_link([]) do
IO.puts "starting supervisor, name is #{__MODULE__}"
Supervisor.start_link(__MODULE__, [])
end
def init(arg) do
IO.puts "initted with arg: #{arg}"
children = [
worker(MayRaiseGenServer, [])
]
supervise(children, strategy: :one_for_one, restart: :transient, name: __MODULE__)
end
end
MayRaiseSupervisor.start_link([])
IO.inspect MayRaiseGenServer.maybe_will_raise
:timer.sleep(2000)
IO.puts "after sleep"
Сначала я только видел сообщение для запуска GenServer один раз, но теперь я вижу его снова. Здесь вывод:
starting supervisor, name is Elixir.MayRaiseSupervisor
initted with arg:
started MyServer, name is Elixir.MayRaiseGenServer
maybe_will_raise called!
number is 14
started MyServer, name is Elixir.MayRaiseGenServer
11:32:28.807 [error] GenServer MayRaiseGenServer terminating
** (RuntimeError) 14
lib/mini.ex:20: MayRaiseGenServer.handle_call/3
(stdlib) gen_server.erl:615: :gen_server.try_handle_call/4
(stdlib) gen_server.erl:647: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: :maybe_will_raise
State: []
** (exit) exited in: GenServer.call(MayRaiseGenServer, :maybe_will_raise, 5000)
** (EXIT) an exception was raised:
** (RuntimeError) 14
lib/mini.ex:20: MayRaiseGenServer.handle_call/3
(stdlib) gen_server.erl:615: :gen_server.try_handle_call/4
(stdlib) gen_server.erl:647: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
(elixir) lib/gen_server.ex:604: GenServer.call/3
lib/mini.ex:45: (file)
(elixir) lib/code.ex:363: Code.require_file/2
Из приведенного выше результата мне не очень понятно, что произойдет. Похоже, что GenServer перезапущен, на основе сообщения, отображаемого в IO, но почему исключение снова выбрасывается? Кроме того, в этом коде:
MayRaiseSupervisor.start_link([])
IO.inspect MayRaiseGenServer.maybe_will_raise
:timer.sleep(2000)
IO.puts "after sleep"
Если вызов метода MayRaiseGenServer.maybe_will_raise
действительно вызовет ошибку, он выглядит как строки после, тот, у которого timer.sleep
и IO.puts
больше не будет запущен. Даже если я изменил код, чтобы попытаться обработать исключение, например:
MayRaiseSupervisor.start_link([])
try do
IO.inspect MayRaiseGenServer.maybe_will_raise
rescue
RuntimeError -> IO.puts "there was an error"
end
:timer.sleep(2000)
IO.puts "after sleep"
Я до сих пор не могу добраться до последнего IO.puts
(если была ошибка). Есть ли способ обработки вызова maybe_will_raise
, который позволил бы мне справиться с этим, создав ошибку и продолжая выполнение? Я предполагаю, что супервизоры не будут автоматически повторять кусок кода при перезапуске.
Ответы
Ответ 1
Как моя точка зрения.
В приведенном выше выводе указывается трассировка стека при возникновении исключения с сигналом выхода в GenServer.call(MayRaiseGenServer, :maybe_will_raise, 5000)
и журналом ошибок, поскольку terminate/2
вызывается с причиной {%RuntimeError{message: ...}, [...]
.
Вы можете определить обратный вызов terminate/2
, чтобы увидеть:
def terminate(reason, _state) do
IO.inspect reason
end
Terminate/2
Если причина не такова: normal,: shutdown nor {: shutdown, term}, ошибка вход.
Но когда в обратном вызове GenServer возникло исключение (кроме init/1
), он вызывает terminate/2
, сообщающий, что сервер собирается выйти (был отправлен сигнал на выход).
Итак, код после этой строки не будет выполнен:
try do
IO.inspect MayRaiseGenServer.maybe_will_raise
...
но не должен ли вывод IO.puts "запускать MyServer" снова?
А также, когда ваш GenServer завершен. Ваш супервизор начнет новый, связанный с основным процессом с вашим процессом GenServer (ваш MayRaiseGenServer.start_link
снова получил вызов)
Последнее, если вы хотите, чтобы код продолжал выполнение. Вы можете поймать сигнал выхода следующим образом:
MayRaiseSupervisor.start_link([])
try do
IO.inspect MayRaiseGenServer.maybe_will_raise
catch
:exit, _ -> IO.puts "there was an error"
end
:timer.sleep(2000)
IO.puts "after sleep"
Но я думаю, вам стоит подумать о том, чтобы использовать raise
в обратном вызове GenServer. Надеемся, что помогите!
Ответ 2
Проблема заключается в том, что ваш диспетчер не связан с GenServer. Поэтому он не уведомляется о смерти ребенка (через сигнал выхода). Чтобы исправить это, вам нужно использовать GenServer.start_link/3
.
Для получения дополнительной информации проверьте Erlang или Elixir документация по процессам.
Ответ 3
По ошибке я использовал GenServer.start
вместо GenServer.start_link
и искал ответ в течение двух дней.
Примечание 1: Дерево супервизоров в :observer.start
помогло отладить эту проблему
Примечание 2: Но проблема выше, используя только start_link
. Я пишу для людей, которые совершают ту же ошибку, что и я: -P.
Ответ 4
Сервер перезапускается. Чтобы доказать это, я добавил этот фрагмент
spawn fn ->
:timer.sleep(1000)
IO.inspect GenServer.call(__MODULE__, :maybe_will_raise)
end
в двух местах. Один в обратном вызове handle_call, и я также добавил его в функцию init. Вот полный модуль после этого
defmodule MayRaiseGenServer do
@moduledoc false
use GenServer
def start_link do
IO.puts "started MyServer, name is #{__MODULE__}"
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def init(_) do
spawn fn ->
:timer.sleep(1000)
IO.inspect GenServer.call(__MODULE__, :maybe_will_raise)
end
{:ok, nil}
end
def maybe_will_raise do
GenServer.call(__MODULE__, :maybe_will_raise)
end
def handle_call(:maybe_will_raise,_from, state) do
IO.puts "maybe_will_raise called!"
:random.seed(:erlang.now)
number = Enum.to_list(1..100) |> Enum.shuffle |> List.first
IO.puts "number is #{number}"
if rem(number,2) != 0 do
raise "#{number}"
end
spawn fn ->
:timer.sleep(1000)
IO.inspect GenServer.call(__MODULE__, :maybe_will_raise)
end
{:reply, {"You got lucky"}, state}
end
end
Тогда вам не нужно вызывать
IO.inspect MayRaiseGenServer.maybe_will_raise
После
MayRaiseSupervisor.start_link([])
Попробуйте и посмотрите