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?
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.