Genserver timeout error on :no_reply response

I have GenServer setup in a module. One of the handle_call/3 returns {:no_reply, state}. Every time after executing the call a timeout error raises. Can someone tell me what I am doing wrong here? This is the first time I am trying GenServer.

My code looks like this:

defmodule TestApp.Message do
  alias TestApp.Message

  use GenServer
  import Logger

  def init(args) do
    {:ok, args}
  end

  def start_link(_) do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def handle_call({%{"message" => message}, state}, _sender, _current_state) do
    info("message")
    {:noreply, state}
  end
end

This is the error message:

{:timeout, {GenServer, :call, [MyApp.Message, {%{"message" => message}, %{id: 1, sender: "user1}}, 5000]}}

The {:noreply, state} return value tells the behaviour not to send a reply to the GenServer.call. As it has built-in timeout of 5 seconds (which can be changed) then it will timeout when a reply does not arrive within that time. You need to explicitly send back a reply by returning {:reply, reply, state}.

1 Like

I am planning to set up a connection with an iOT device, which does not expect a reply there. In such cases what can I do?

You need to use handle_cast in this case…

2 Likes

Yes, one way is to use GenServer.cast/handle_cast. One problem with that is that is gives no guarantees of the destination being there or the message arriving as it literally uses :erlang.send internally. For more safety you could use GenServer.call/handle_call and return the reply :ok. NB that this is doing a synchronous call with all that it costs but you will at least be certain the message has arrived and has been processed.

It’s all a matter of deciding what you need/want and are willing to pay for it. TANSTAAFL

2 Likes

In other words, when you use handle_call, your caller (the process that calls GenServer.call) expects a reply. One can return {:noreply, state} from handle_call, but only if the reply is computed asynchronously and then sent later with GenServer.reply.

As @rvirding and @kokolegorille wrote, you have two options:

  • Return a simple reply like {:reply, :ok, state}. This ensures that GenServer.call will return :ok only after the operation that you perform in the GenServer completes. This is, generally, the best way, as it ensures that whatever you wanted to execute actually does execute.

  • Alternatively, if you really don’t care about the answer, and also you don’t need to guarantee that the GenServer actually performed the operation, you can use GenServer.cast and handle_cast.