Is it recommended to create a GenServer to make gRPC calls?

In my application, I need to make a lot of gRPC calls to a Python server. Is it recommended to create a GenServer to make those calls?

I’m trying to do the following:

defmodule MyApp.ClassifierServer do
  use GenServer

  alias MyApp.ClassifierImpl

  @name __MODULE__

  def start_link(_) do
    classifier_host = Application.get_env(:myapp, MyApp.ClassifierServer)[:hostname]
    {:ok, channel} = GRPC.Stub.connect(classifier_host)
    GenServer.start_link(@name, channel, name: @name)
  end

  def init(channel) do
    # Fazer uma verificação do status do serviço.
    IO.puts("Starting channel to Classifier Server")
    {:ok, channel}
  end

  def handle_call({:classify, params}, _from, channel) do
    case ClassifierImpl.classify(params, channel) do
      {:ok, response} ->
        {:reply, response, channel}

      {:error, error_message} ->
        {:reply, error_message, channel}
    end
  end
end

When I start my application, I get the following message:

[error] Unexpected event in state :connected of type :info:
{:timeout, #Reference<0.3273821823.2036334600.82725>, {:cow_http2_machine, :settings_timeout}}
{:state, #PID<0.604.0>, {:up, #Reference<0.3273821823.2036334600.82721>}, 'localhost', 50051, "http", 'localhost', 50051, [], %{protocols: [:http2], retry: 100, retry_fun: &GRPC.Adapter.Gun.retry_fun/2, tcp_opts: [nodelay: true], transport: :tcp}, #Reference<0.3273821823.2036334600.82726>, #Port<0.23>, :gun_tcp, true, {:tcp, :tcp_closed, :tcp_error}, :gun_http2, {:http2_state, #Port<0.23>, :gun_tcp, %{initial_connection_window_size: 8000000, initial_stream_window_size: 8000000}, [:gun_data_h], "", :connected, {:http2_machine, :client, %{initial_connection_window_size: 8000000, initial_stream_window_size: 8000000}, :normal, :undefined, #Reference<0.3273821823.2036334600.82725>, %{initial_window_size: 65535}, %{initial_window_size: 8000000}, %{initial_window_size: 4194304, max_frame_size: 4194304, max_header_list_size: 8192}, 4194304, 8000000, 1, 0, [], [], [], {:state, 0, 4096, 4096, []}, {:state, 0, 4096, 4096, []}}, %{}, %{}}, :gun_default_event_h, :undefined}

and the supervisor tries to restart the server every time I get this error.

I found out that the error is a problem with the gRPC library for Elixir. It won’t block remote calls to server, but it will keep showing this error message. But the question is still open, is it recommended to create a GenServer to make gRPC calls?

I’d say yes, use GenServer. If not for anything else, you’ll at least guarantee you don’t overwhelm the remote server or kick off its DDoS defenses.

(Because a process can only process one message at a time and if you send 100 requests to your GenServer it will process them one by one, serially.)

…Or use a pooling library like poolboy.

Some good reading on this topic if you haven’t already read it is https://www.theerlangelist.com/article/spawn_or_not

3 Likes

It probably depends on the grpc library. Looking at your example code I would not suggest the same when using grpcbox (https://github.com/tsloughter/grpcbox). grpcbox has client side load balancing and handles channels/subchannels for you, so keeping a connection in a genserver state is not the recommended way to go.

And while grpcbox is Erlang, I know there are production users who are using it from Elixir.

5 Likes