Search code examples
elixirgen-server

Is a GenServer :continue call synchronous?


A GenServer handle_call/3 implementation can return :continue to invoke an additional function. Are there any guarantees as to when this function will run relative to other messages?

For example, consider this module that just keeps a running counter:

defmodule Tmp do
  use GenServer

  def start_link(opts), do: GenServer.start_link(__MODULE__, 0, opts)
  def incr(tmp), do: GenServer.call(tmp, :incr)

  @impl true
  def init(state), do: {:ok, state}

  @impl true
  def handle_call(:incr, _from, n) do
    {:reply, n, n, {:continue, :incr}}
  end

  @impl true
  def handle_continue(:incr, n) do
    {:noreply, n+1}
  end
end

When you call Tmp.incr/1, the handle_call/3 method returns the current value of the counter, but then also returns :continue. This causes the GenServer infrastructure to call handle_continue/2.

If I call Tmp.incr/1 twice in succession, am I guaranteed to get incrementing values? Or is it possible that handle_call/3 will be called twice before handle_continue/2 is called at all?

iex> {:ok, tmp} = Tmp.start_link([])
iex> Tmp.incr(tmp)
0
iex> Tmp.incr(tmp)
1
# If I type fast enough, will this ever return 0?

Solution

  • Yes, :continue is synchronous - the continue will be executed immediately after the function you returned it from. Its use-case is exactly that: guaranteeing it will be run right after, which could not be guaranteed by other methods such as :timeout or sending a message to yourself with send(self(), :incr).

    This is briefly mentioned in the documentation for GenServer callbacks:

    Returning {:reply, reply, new_state, {:continue, continue}} is similar to {:reply, reply, new_state} except handle_continue/2 will be invoked immediately after with the value continue as first argument.

    Also on the Timeout section:

    Because a message may arrive before the timeout is set, even a timeout of 0 milliseconds is not guaranteed to execute. To take another action immediately and unconditionally, use a :continue instruction.