Контролируемый 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([])

Попробуйте и посмотрите