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.