lib/gen_server/proxy/caller.ex

defmodule GenServer.Proxy.Caller do
  use PersistConfig

  alias GenServer.Proxy
  alias GenServer.Proxy.{Log, Timer}

  @timeout get_env(:timeout)
  @times get_env(:times)

  @spec call(Proxy.server_id(), term, timeout, module) :: term | {:error, term}
  def call(server_id, request, timeout, module) do
    server = module.server_name(server_id)

    try do
      GenServer.call(server, request, timeout)
    catch
      # Reason is typically {:killed | :noproc, mfargs},
      # where mfargs is {GenServer, :call, [server, request, timeout]}.
      # Whatever the reason, we wait expecting OTP to fix the issue...
      :exit, reason ->
        failed = {:call, 3, server, @timeout, @times, reason, __ENV__}
        :ok = Log.warn(:failed, failed)
        :ok = Timer.wait(server)

        try do
          GenServer.call(server, request, timeout)
        catch
          :exit, reason ->
            :ok = Log.warn(:failed_again, {:call, 3, server, reason, __ENV__})
            module.server_unregistered(server_id)
            {:error, reason}
        end
    end
  end
end