Out of curiosity I started to learn how to gracefully shutdown an Elixir application, but I have some trouble understanding the behavior of GenServer terminate/2
.
So far I have understood that:
- calling
System.stop()
,:init.stop()
orc:q()
from IEx will properly shutdown the application and the Elixir VM. - calling
Application.stop(:myapp)
from IEx will properly shutdown the application but keep the Elixir VM alive.
If one wants to do some clean-up in a GenServer during the shutdown, one must use Process.flag(:trap_exit, true)
in the init/1
of said GenServer and perform the clean-up in terminate/2
.
What I am not sure of:
Calling Process.exit(pid, :shutdown)
(where pid
is the GenServer pid) will effectively send an info message with 'EXIT'
to the GenServer but terminate/2
will not be called and the GenServer is still alive.
Is this because the exit signal is not sent from the parent but from IEx?
The documentation states:
terminate/2
is called if a callback (exceptinit/1
) does one of the following:
- (…)
- the
GenServer
traps exits (usingProcess.flag/2
) and the parent process sends an exit signal
What I don’t understand:
Calling Supervisor.stop(Myapp.Supervisor)
will call terminate/2
but no info message is received by the GenServer. Since Process.exit(child_pid, :shutdown)
is called automatically in this case, I was expecting to also receive an info message.
The documentation states:
The termination happens by sending a shutdown exit signal, via
Process.exit(child_pid, :shutdown)
, to the child process and then awaiting for a time interval for the child process to terminate.
Example:
The following mix.exs
shows that:
- calling
Process.exit(pid, :shutdown)
from IEx will not call the GenServerterminate/2
, the GenServer is still alive. - calling
Supervisor.stop(Myapp.Supervisor)
from IEx will not send an info message to the GenServer.
defmodule Myapp.MixProject do
use Mix.Project
def project do
[
app: :myapp,
version: "0.1.0",
elixir: "~> 1.7",
start_permanent: Mix.env() == :prod,
]
end
def application do
[
extra_applications: [:logger],
mod: {Myapp.Application, []}
]
end
end
defmodule Myapp.Application do
use Application
def start(_type, _args) do
children = [
{Myapp.Worker, name: Myapp.Worker},
]
opts = [strategy: :one_for_one, name: Myapp.Supervisor]
Supervisor.start_link(children, opts)
end
end
defmodule Myapp.Worker do
use GenServer
def start_link(opts) do
GenServer.start_link(__MODULE__, :ok, opts)
end
def init(:ok) do
Process.flag(:trap_exit, true)
{:ok, nil}
end
def handle_call(:ping, _from, state) do
{:reply, :pong, state}
end
def handle_info(msg, state) do
IO.puts "message received by #{__MODULE__}: #{inspect msg}"
{:noreply, state}
end
def terminate(reason, _state) do
IO.puts "#{__MODULE__}.terminate/2 called wit reason: #{inspect reason}"
end
end