Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Supervised GenServer not being restarted?

I've reduced the size of the question, cause it was too big. Here's the code:

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"

Initially, I only saw the message for starting the GenServer once, but now I see it again. Here's the output:

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

From the above output, it's not very clear to me what happens. It looks like the GenServer is restarted, based on the message shown on IO, but why is the exception being thrown again? Also, in this code:

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

If the method call MayRaiseGenServer.maybe_will_raise will indeed raise an error, it looks like the lines after, the one with timer.sleep and the IO.puts won't be run anymore. Even if I change the code to try and handle the exception, like this:

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"

I still cannot seem to reach the last IO.puts ( if there was an error ). Is there a way of handling the call to maybe_will_raise that would allow me to handle it raising an error, and continuing execution? I'm guessing supervisors won't automatically retry a piece of code when they're being restarted.

like image 737
Geo Avatar asked Sep 01 '16 18:09

Geo


2 Answers

As my point of view.

Your output above is telling you a stack trace when an exception was raised with an exit signal in GenServer.call(MayRaiseGenServer, :maybe_will_raise, 5000) and an error log because terminate/2 is invoked with a reason {%RuntimeError{message: ...}, [...].

You can define terminate/2 callback to see:

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

Terminate/2

If reason is not :normal, :shutdown nor {:shutdown, term} an error is logged.

But when an exception was raised inside a GenServer callback (except init/1) it will invoke terminate/2 that telling the server is about to exit (Exit signal was sent).

So code after this line will not be executed:

try do
IO.inspect MayRaiseGenServer.maybe_will_raise
...

but shouldn't the IO.puts "started MyServer" output appear again?

And also when your GenServer is exited.Your supervisor will start a new one linking the main process with your GenServer process (Your MayRaiseGenServer.start_link got call again)

The last thing is if you want to make the code continue executing.You can catch exit signal like this:

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"

But i think you should consider of using raise in your GenServer callback.Hope that help!

like image 61
TheAnh Avatar answered Oct 16 '22 21:10

TheAnh


The problem is that your Supervisor is not linked to the the GenServer. Therefore it is not notified about death of the child (through an exit signal). To fix that, you need to use GenServer.start_link/3.

For more information check either Erlang or Elixir documentation on processes.

like image 3
mentels Avatar answered Oct 16 '22 19:10

mentels