GenServer timeout and retry

Just started learning about OTP. Say if i have a GenServer that timesout

@impl true
  def handle_call(:state, _from, state) do
    Process.sleep(6000)
    {:reply, state, state}
  end

I want to try to capture the fact that it timesout and retry the function again. Somehow the rescue block is not triggered. May i know what am i understanding wrongly here?

def state(calculator) do
    try do
      GenServer.call(calculator, :state)
    rescue
      e ->
        IO.puts("Fail state because: #{e}")
        state(calculator)
    end
  end

Try using catch instead of rescue.
I found this post informative: https://cultivatehq.com/posts/genserver-call-timeouts/

I found out what’s the issue here

def state(calculator) do
    try do
      GenServer.call(calculator, :state)
    catch
      :exit, _ ->
        IO.puts("there was an error")

      e ->
        IO.puts("Fail state because: #{e}")
        state(calculator)
    end
  end

First catch clause works but not the other which puzzles me as i thought e -> ... should be match for everything. Can someone explain to me what’s happening here? Also in future how can i tell what kind of catch clause i should use in some other scenarios.

Found something useful here: https://stackoverflow.com/questions/40280887/elixir-try-catch-vs-try-rescue

creating | handling with | where y is | and z is
-----------------------------------------------------------------
raise x  | catch y, z    | :error     | %RuntimeError{message: x}
error(x) | catch y, z    | :error     | x
throw x  | catch y, z    | :throw     | x
exit(x)  | catch y, z    | :exit      | x

It’s because Elixir has three types of error mechanisms: errors (raise/1, raise/2), throws and exits. Generally, when you use try/catch, then a type of error is implicitly set to throw. Here is an example:

try do
  throw(:value)
catch
  e -> IO.inspect(e)
end

# This is "expanded" to the following code:

try do
  throw(:value)
catch
  :throw, e -> IO.inspect(e)
end

So if you want to catch exits, then you have to explicitly define this in your expression like this: catch :exit, e -> .... In your code e -> ... is expanded to :throw, e -> ... so it can catch only throws, other kinds of errors will be ignored.

I recommend to check the following links (first one should be enough to master this topic):

Oh, there is also one cool trick to skip one indentation in your code:

def state(calculator) do
  GenServer.call(calculator, :state)
catch
  :exit, _reason ->
    IO.puts("something gone wrong")
end

As you can see you can skip try do when you want to catch errors in the whole function block.

1 Like