GenServer init/1 must defer real work to handle_continue/2.
init/1 blocks GenServer.start_link/3 and with it everything else the supervisor
still has to start. An init/1 that hits the database, another process or the
network delays the whole supervision tree and risks the five second init timeout.
Build the initial state, return {:ok, state, {:continue, term}}, and do the work
in handle_continue/2.
# BAD — blocks the supervisor while the query runs
defmodule MyApp.Server do
use GenServer
def init(opts) do
rows = MyApp.Repo.all(MyApp.Row)
{:ok, %{rows: rows, opts: opts}}
end
end
# GOOD — init/1 only builds state, the query runs in handle_continue/2
defmodule MyApp.Server do
use GenServer
def init(opts) do
{:ok, %{rows: [], opts: opts}, {:continue, :load}}
end
def handle_continue(:load, state) do
{:noreply, %{state | rows: MyApp.Repo.all(MyApp.Row)}}
end
endThis check is a heuristic. Files without a literal use GenServer are skipped
entirely. In files that have one, an init/1 clause is flagged when it contains a
remote call not covered by the :allowed_modules list and no {:continue, _}
tuple anywhere in the clause. Each clause is judged on its own — a {:continue, _}
in one clause does not excuse blocking work in another — and each violating clause
produces one issue, anchored at its first disallowed call.
The allow-list mixes whole modules and single functions. A bare module entry
(Keyword, Logger) allows every call on it; a {module, function} tuple grants
one function surgically. The defaults allow the cheap init idioms
Process.flag/2, Process.monitor/1 and Process.send_after/3 without allowing
Process wholesale — so a blocking Process.sleep/1 in init/1 stays flagged.
Known approximations, chosen to keep the check cheap and false positives rare:
- A
{:continue, _}tuple anywhere in a clause counts as deferring, even when it is not in return position. - Local function calls are always allowed — the check cannot cheaply resolve what a private helper does.
- Struct literals (
%__MODULE__{},%SomeStruct{}) and calls on__MODULE__are allowed. use GenServeris detected per file, so everyinit/1in a file that uses GenServer anywhere is checked, and auseinjected by another macro's__using__is invisible.- Aliases are not resolved — a module is matched by the name it is written as, so
alias MyApp.Repofollowed byRepo.all/1is flagged asRepo, notMyApp.Repo.