监督的GenServer没有重启?

时间:2016-09-01 18:50:02

标签: elixir otp

我缩小了问题的大小,因为它太大了。这是代码:

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

从上面的输出中,我不太清楚会发生什么。根据IO上显示的消息,看起来GenServer重新启动,但为什么再次抛出异常?此外,在此代码中:

MayRaiseSupervisor.start_link([])
IO.inspect MayRaiseGenServer.maybe_will_raise
:timer.sleep(2000)
IO.puts "after sleep"

如果方法调用MayRaiseGenServer.maybe_will_raise确实会引发错误,则看起来后面的行,timer.sleepIO.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的调用,这将允许我处理它引发错误,并继续执行?我猜测主管在重启时不会自动重试一段代码。

4 个答案:

答案 0 :(得分:4)

作为我的观点。

上面的输出告诉您在GenServer.call(MayRaiseGenServer, :maybe_will_raise, 5000)中使用退出信号引发异常时的堆栈跟踪以及错误日志,因为terminate/2的调用原因为{%RuntimeError{message: ...}, [...]

您可以定义terminate/2回调以查看:

def terminate(reason, _state) do
  IO.inspect reason
end

Terminate/2

  

如果原因不是:正常,则:shutdown和{: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"

但我认为您应该考虑在GenServer回调中使用raise。希望有所帮助!

答案 1 :(得分:3)

问题是您的Supervisor未链接到GenServer。因此,没有通知孩子的死亡(通过退出信号)。要解决此问题,您需要使用GenServer.start_link/3

有关详细信息,请查看有关流程的ErlangElixir文档。

答案 2 :(得分:0)

错误地,我使用了GenServer.start代替GenServer.start_link并搜索了两天的答案。

注1: :observer.start中的主管树帮助调试了这个问题

注2:但仅限使用start_link的问题。我正在为像我一样犯同样错误的人写信:-P。

答案 3 :(得分:0)

正在重启服务器。为了证明这一点,我添加了这个片段

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([])

试试看