Graceful shutdown via trap_exit and terminate/2
Before the code: what graceful shutdown actually means
Picture a restaurant at closing time. The good waiter does not yell stop and dump plates on the floor. They stop seating new guests, let the people already eating finish, then turn the lights off. That is graceful shutdown for a server. When the operating system sends a stop signal, you want the server to stop accepting new connections, let the writes already in flight finish, then quit. On the BEAM, OTP already gives you most of this for free. You opt into the cleanup with Process.flag(:trap_exit, true) and a terminate/2 callback, and OTP handles the rest.
Stop accepting, drain, exit
Quiz: Quiz
Loading practice…
Why is this Elixir's step 9? Because OTP already gives us what other languages had to engineer. The whole step is one Process.flag call plus one terminate/2 callback.
def init(_) do
Process.flag(:trap_exit, true)
store = case File.read(@rdb_path) do
{:ok, bin} -> :erlang.binary_to_term(bin)
{:error, _} -> %{}
end
{:ok, store}
end
def terminate(reason, state) do
IO.puts("[shutdown] terminate received (#{inspect(reason)})")
tmp = "#{@rdb_path}.tmp"
File.write!(tmp, :erlang.term_to_binary(state))
File.rename!(tmp, @rdb_path)
:ok
endinit enables trap_exit. terminate/2 fires on shutdown and snapshots.
Where is the in-flight semaphore from the other siblings? It is not needed. Every connection is its own BEAM process. The supervisor's shutdown timeout (default 5s) lets each child finish its current call. OTP does the bookkeeping.
When the supervisor sends a normal shutdown. In production: Application.stop -> Supervisor sends :shutdown -> each child runs terminate/2. In iex: you may need to press Ctrl+C twice; the IEx shell traps the first one. In a real release (mix release), bin/myapp stop triggers a graceful shutdown cleanly.
Quiz: Quiz
Loading practice…