zakora
Trying to understand GenServer terminate/2 behavior when trapping exit
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/2is called if a callback (exceptinit/1) does one of the following:
- (…)
- the
GenServertraps 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
Marked As Solved
michalmuskala
One of the elements of the contract of the OTP special process (and GenServer is such a process) is to die when it receives an EXIT message from parent. That’s what you’re seeing - the parent EXIT message is intercepted by GenServer internals and causes a suicide of the server.
If the special process is set to trap exits and if the parent process terminates, the expected behavior is to terminate with the same reason:
Also Liked
zakora
tty
If one wants to do some clean-up in a GenServer during the shutdown, one must use
Process.flag(:trap_exit, true)in theinit/1of said GenServer and perform the clean-up interminate/2.
According to the GenServer docs the one must use Process.flag(:trap_exit, true) in the init/1 is inaccurate. It is one of the 5 possible conditions terminate/2 is called, not the only condition.
Last Post!
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









