受监督的 GenServer 没有重新启动?
Supervised GenServer not being restarted?
我缩小了问题的大小,因为它太大了。这是代码:
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.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
的调用,使我能够处理它并引发错误并继续执行?我猜主管在重启时不会自动重试一段代码。
问题是您的 Supervisor 没有链接到 GenServer。因此,它不会收到有关 child 死亡的通知(通过退出信号)。要解决此问题,您需要使用 GenServer.start_link/3
.
作为我的观点。
您上面的输出告诉您当在 GenServer.call(MayRaiseGenServer, :maybe_will_raise, 5000)
中用退出信号引发异常时的堆栈跟踪和错误日志,因为 terminate/2
被调用的原因 {%RuntimeError{message: ...}, [...]
。
可以定义terminate/2
回调查看:
def terminate(reason, _state) do
IO.inspect reason
end
If reason is not :normal, :shutdown nor {:shutdown, term} an error is
logged.
但是当在 GenServer 回调中引发异常时(init/1
除外)它将调用 terminate/2
告诉服务器即将退出(已发送退出信号)。
所以这一行之后的代码不会被执行:
try do
IO.inspect MayRaiseGenServer.maybe_will_raise
...
but shouldn't the IO.puts "started MyServer" output appear again?
而且当您的 GenServer 是 exited.Your 时,主管将启动一个新的将主进程与您的 GenServer 进程链接的进程(您的 MayRaiseGenServer.start_link
再次接到电话)
最后一件事是如果你想让代码继续executing.You可以像这样捕捉退出信号:
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 callback.Hope 中使用 raise
这有帮助!
我错误地使用了GenServer.start
而不是GenServer.start_link
并搜索了两天的答案
注意 1: :observer.start
中的主管树对调试此问题有很大帮助
注2: 但以上问题仅使用start_link
。我正在写给和我一样犯同样错误的人:-P.
正在重新启动服务器。为了证明这一点,我添加了这个片段
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([])
试试看
我缩小了问题的大小,因为它太大了。这是代码:
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.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
的调用,使我能够处理它并引发错误并继续执行?我猜主管在重启时不会自动重试一段代码。
问题是您的 Supervisor 没有链接到 GenServer。因此,它不会收到有关 child 死亡的通知(通过退出信号)。要解决此问题,您需要使用 GenServer.start_link/3
.
作为我的观点。
您上面的输出告诉您当在 GenServer.call(MayRaiseGenServer, :maybe_will_raise, 5000)
中用退出信号引发异常时的堆栈跟踪和错误日志,因为 terminate/2
被调用的原因 {%RuntimeError{message: ...}, [...]
。
可以定义terminate/2
回调查看:
def terminate(reason, _state) do
IO.inspect reason
end
If reason is not :normal, :shutdown nor {:shutdown, term} an error is logged.
但是当在 GenServer 回调中引发异常时(init/1
除外)它将调用 terminate/2
告诉服务器即将退出(已发送退出信号)。
所以这一行之后的代码不会被执行:
try do
IO.inspect MayRaiseGenServer.maybe_will_raise
...
but shouldn't the IO.puts "started MyServer" output appear again?
而且当您的 GenServer 是 exited.Your 时,主管将启动一个新的将主进程与您的 GenServer 进程链接的进程(您的 MayRaiseGenServer.start_link
再次接到电话)
最后一件事是如果你想让代码继续executing.You可以像这样捕捉退出信号:
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 callback.Hope 中使用 raise
这有帮助!
我错误地使用了GenServer.start
而不是GenServer.start_link
并搜索了两天的答案
注意 1: :observer.start
中的主管树对调试此问题有很大帮助
注2: 但以上问题仅使用start_link
。我正在写给和我一样犯同样错误的人:-P.
正在重新启动服务器。为了证明这一点,我添加了这个片段
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([])
试试看