The Author Online Book Forums are Moving

The Author Online Book Forums will soon redirect to Manning's liveBook and liveVideo. All book forum content will migrate to liveBook's discussion forum and all video forum content will migrate to liveVideo. Log in to liveBook or liveVideo with your Manning credentials to join the discussion!

Thank you for your engagement in the AoF over the years! We look forward to offering you a more enhanced forum experience.

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