Catching genserver exits in calling code

Can anyone explain the right way to handle this? I have some code that runs in a Task, which calls out to a library function. That function internally makes use of a GenServer, and in some circumstances the server terminates. I’d like my calling code to detect this failure, so I can update the task status, but so far I haven’t figured out how to stop the task terminating along with the server it calls (even though as far as I can tell they’re not linked). It seems like I need to trap exits somewhere, but I haven’t been able to figure out where or how.

Here’s an extremely simplified application that demonstrates the behaviour:


# the genserver that crashes – pretend this is in an external dependency
defmodule Crash.Worker do
  use GenServer
  def start_link(arg), do: GenServer.start_link(__MODULE__, arg, name: __MODULE__)
  def init(_), do: {:ok, nil}
  def crash, do: GenServer.call(__MODULE__, :unhandled)
end

# calling code
defmodule Crash do
  def run do
    IO.puts("Calling crashing genserver")
    Crash.Worker.crash()
    IO.puts("Done")
  end
end

Output (never gets to the Done):

crash (main)$ iex -S mix
Erlang/OTP 25 [erts-13.0.4] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Crash.run
Calling crashing genserver

14:52:34.065 [error] GenServer Crash.Worker terminating
** (RuntimeError) attempted to call GenServer Crash.Worker but no handle_call/3 clause was provided
    (crash 0.1.0) lib/gen_server.ex:787: Crash.Worker.handle_call/3
    (stdlib 4.0.1) gen_server.erl:1146: :gen_server.try_handle_call/4
    (stdlib 4.0.1) gen_server.erl:1175: :gen_server.handle_msg/6
    (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.154.0>): :unhandled
State: nil
Client #PID<0.154.0> is alive

    (stdlib 4.0.1) gen.erl:256: :gen.do_call/4
    (elixir 1.14.0) lib/gen_server.ex:1035: GenServer.call/3
    (crash 0.1.0) lib/crash.ex:10: Crash.run/0
    (stdlib 4.0.1) erl_eval.erl:744: :erl_eval.do_apply/7
    (elixir 1.14.0) src/elixir.erl:288: :elixir.eval_forms/3
    (elixir 1.14.0) lib/module/parallel_checker.ex:100: Module.ParallelChecker.verify/1
    (iex 1.14.0) lib/iex/evaluator.ex:329: IEx.Evaluator.eval_and_inspect/3
    (iex 1.14.0) lib/iex/evaluator.ex:303: IEx.Evaluator.eval_and_inspect_parsed/3
** (exit) exited in: GenServer.call(Crash.Worker, :unhandled, 5000)
    ** (EXIT) an exception was raised:
        ** (RuntimeError) attempted to call GenServer Crash.Worker but no handle_call/3 clause was provided
            (crash 0.1.0) lib/gen_server.ex:787: Crash.Worker.handle_call/3
            (stdlib 4.0.1) gen_server.erl:1146: :gen_server.try_handle_call/4
            (stdlib 4.0.1) gen_server.erl:1175: :gen_server.handle_msg/6
            (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
    (elixir 1.14.0) lib/gen_server.ex:1038: GenServer.call/3
    (crash 0.1.0) lib/crash.ex:10: Crash.run/0
1 Like

Try something like this

try do
  {:ok, Crash.Worker.crash()}
catch
  :exit, e ->
    {:error, e}
end

You can read more here:
https://hexdocs.pm/elixir/Kernel.SpecialForms.html#try/1-catching-values-of-any-kind


PS:
Please don’t forget to mark the correct answer

Perfect! Thank you :pray:t2: