Supervisor.terminate_child doesn't call terminate callback on child

I was trying to stop children of a Supervisor through terminate_child/2 and expected that the GenServer callback terminate/2 is called on the child but it is not.
So now I am curious if this is correct and when to use Supervisor.terminate_child/2 over GenServer.stop/1.
Here is an example:

defmodule Test do
  use GenServer
  
  def start_link(_) do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end
  
  def init(args) do
  {:ok, args}
  end
  
  def handle_call(:test, _from, state) do
    {:reply, :test_reply, state}
  end
  
  def terminate(_reason, _state) do
    IO.inspect("terminate")
  end
end

{:ok, pid} = Supervisor.start_link([Test], strategy: :one_for_one)

GenServer.call(Test, :test)
|> IO.inspect()
# :test_reply

Supervisor.terminate_child(pid, Test)
|> IO.inspect()
# :ok 
# but no 'terminate'

Thanks for your insights!

You GenServer is not trapping exits so when the supervisor sends it an exit signal to terminate the GenServer it just immediately dies. You need to implement an init/1 callback in which you do, amongst other things, a Process.flag(:trap_exit, true) which then makes the GenServer process catch that exit signal, call the terminate/2 callback and then die.

3 Likes

Thanks! Sorry I should have read the docs more carefully. Its clearly written there: https://hexdocs.pm/elixir/GenServer.html#c:terminate/2

1 Like