OTP 21 introduces handle_continue
callback to GenServer
When using GenServers, it is sometimes necessary to perform long-running code during the initialization. The common pattern for handling this is to send a message to
self()
from within the
init
callback, and then perform the long-running work within the
handle_info
function for the message we just sent:
defmodule Server do
use GenServer
def init(arg) do
send(self(), :finish_init)
state = do_quick_stuff(arg)
{:ok, state}
end
def handle_info(:finish_init, state) do
state = do_slow_stuff(state)
{:noreply, state}
end
# edited for brevity
end
By performing only quick operations within
init/1
itself, the GenServer can return sooner to the caller (i.e. the caller won’t be blocked unnecessarily). Then, the GenServer will process the
:finish_init
message it sent itself and finish its initialization in parallel. Future messages it will receive will then be processed with a fully initialized state.
There is a risk for race conditions, however: clients could have sent messages to the GenServer that arrive before the
:finish_init
call. This case is even more likely to happen with named GenServers, as clients could send messages at any time (using only the process’ name as they won’t require to know the pid).
OTP 21 introduces the
handle_continue/2
callback. With this concept, when a
:continue
response is returned by a callback, the corresponding
handle_continue/2
function is called, but no messages from the inbox are processed until it has been executed.
Therefore, when using OTP 21, the code above could be changed to
defmodule Server do
use GenServer
def init(arg) do
state = do_quick_stuff(arg)
{:ok, state, {:continue, :finish_init}}
end
def handle_continue(:finish_init, state) do
state = do_slow_stuff(state)
{:noreply, state}
end
# edited for brevity
end
This way, messages in the process’ mailbox will be ignored until
handle_continue(:finish_init, state)
is done executing and our state is properly initialized. No race conditions, yay!
This is an OTP feature, therefore being able to use it depends on the OTP version you’re running. However, if you want to be able to
@impl
the
handle_continue/2
callback implementation, you’ll need to be using Elixir 1.7 as the GenServer behaviour from earlier versions doesn’t specify this callback.
Would you like to see more Elixir content like this? Sign up to my mailing list so I can gauge how much interest there is in this type of content.