Bibek Pandey (7) [Avatar] Offline
In Section 8.23 where Monitors are discussed, there is a small grey box that mentions that exits are propagated through gen_server calls. How do you trap these?

Let's say you have a module A which is a GenServer

You also have a module B, which also is a GenServer

in one of A's handle_call method's you issue a, :foo)

if The Server Process B crashes during this call, the error will be propagated to Server Process A.

What needs to be done in either modules A or B to trap exits here? I've experimented with trapping exits in module A's init function and supplying the relevant handle_info function but to no success. The Process A dies. I'm assuming the Server Process A is also monitoring Process B.

Is there some different technique to trap exits that are propagated through gen server calls?

Thank you very much!
sjuric (86) [Avatar] Offline
There are two aspects of the answer. The first one is that will raise an exit in the client process if the server process terminates while the client awaits the response. You can catch that exit with try/catch:

try do 
catch :exit, reason -> 
  # do something here

In addition, a crash of the server is propagated to all linked processes. If the client is linked to the server process (directly or indirectly through another non-supervisor process), the exit signal will reach it, and the client will by default crash, even if you're using try/catch. In such case, you also need to trap exits in the client, and handle corresponding messages.

If the client is not linked to the server, trapping exits is not necessary.
Bibek Pandey (7) [Avatar] Offline
Thanks for spelling it out. Yes it works. smilie

defmodule A do
  use GenServer

  def start_link() do
    GenServer.start_link(__MODULE__, nil, name: :a)

  def apple() do, :apple)

  def handle_call(:apple, _from, state) do
    IO.puts "In apple handle_call, going to call banana"

    try do
    catch :exit, reason ->
        IO.puts "caught :exit, reason is #{inspect reason}"
    {:reply, :ok, state}

  def handle_info(msg, state) do
    IO.puts "Handle_info, msg received #{inspect msg}"

    {:noreply, state}


defmodule B do
  use GenServer

  def start() do
    GenServer.start(__MODULE__, nil, name: :b)

  def banana(value) do, {:banana, value})

  def handle_call({:banana, value}, _from, state) do
    IO.puts "In banana handle_call, about to explode"

    case value do
      5 -> raise "Pigs are flying, crashing now"
      _ -> ""

    {:reply, :ok, state}


Interactive Elixir (1.3.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> B.start
{:ok, #PID<0.109.0>}
iex(2)> A.start_link
{:ok, #PID<0.111.0>}
In apple handle_call, going to call banana
In banana handle_call, about to explode
caught :exit, reason is {{%RuntimeError{message: "Pigs are flying, crashing now"}, [{B, :handle_call, 3, [file: 'lib/propagate.ex', line: 50]}, {:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 615]}, {:gen_server, :handle_msg, 5, [file: 'gen_server.erl', line: 647]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 247]}]}, {GenServer, :call, [:b, {:banana, 5}, 5000]}}
12:03:47.149 [error] GenServer :b terminating
** (RuntimeError) Pigs are flying, crashing now
    (propagate) lib/propagate.ex:50: B.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: {:banana, 5}
State: nil

iex(5)> Process.whereis(:a)
iex(6)> Process.whereis(:b)