概述

GenServer的terminate callback在进程退出时会被调用.
但若没有:erlang.process_flag(:trap_exit, true), 进程可能被悄无声息地kill掉, 而不走terminate回调.

示例

App.Supervisor下有一个App.Worker, 代码如下.

defmodule App do  use Application  require Logger  def start(_type, _args) do    children = [      %{id: App.Supervisor, start: {App.Supervisor, :start_link, []}, type: :supervisor}    ]    opts = [strategy: :one_for_one, name: __MODULE__]    Supervisor.start_link(children, opts)  endenddefmodule App.Supervisor do  use Supervisor  require Logger  def start_link() do    Logger.info("app.sup start")    Supervisor.start_link(__MODULE__, [], name: __MODULE__)  end  def init([]) do    children = [      %{id: App.Worker, start: {App.Worker, :start_link, []}, type: :worker}    ]    opts = [strategy: :one_for_one, name: __MODULE__]    Supervisor.init(children, opts)  endenddefmodule App.Worker do  use GenServer  require Logger  require Record  Record.defrecordp(:state, [])  def start_link() do    Logger.info("app.worker start")    GenServer.start_link(__MODULE__, [], name: __MODULE__)  end  def init(_) do    # :erlang.process_flag(:trap_exit, true)    {:ok, nil}  end  def handle_call(:stop, _from, state) do    {:stop, nil, state}  end  def handle_call(:raise, _from, state) do    raise RuntimeError    {:reply, nil, state}  end  def terminate(reason, state) do    Logger.warn("app.worker terminate #{inspect(reason)} #{inspect(state)}")    :ok  endend

没有trap_exit

若没有:erlang.process_flag(:trap_exit, true).

  • Supervisor.stop(App.Supervisor)
  • Supervisor.terminate_child(App.Supervisor, App.Worker)
  • Process.exit(:erlang.whereis(App.Worker), :normal)

均不会触发terminate回调.

  • GenServer.call(App.Worker, :stop)
  • GenServer.call(App.Worker, :raise)

会触发terminate回调.

有trap_exit

  • Supervisor.stop(App.Supervisor)
  • Supervisor.terminate_child(App.Supervisor, App.Worker)

会触发terminate回调.

20:25:00.568 [warn]  app.worker terminate :shutdown nil
  • Process.exit(:erlang.whereis(App.Worker), :normal)

会收到{:EXIT, #PID<0.149.0>, :normal}消息, 若处理了消息, 不会触发terminate回调.

20:25:59.849 [warn]  unknown_info:{:EXIT, #PID<0.134.0>, :normal}
  • Process.exit(:erlang.whereis(App.Worker), :shutdown)
20:27:55.170 [warn]  unknown_info:{:EXIT, #PID<0.134.0>, :shutdown}
  • GenServer.call(App.Worker, :stop)
  • GenServer.call(App.Worker, :raise)

会触发terminate回调.

总结

GenServer msg loop中的逻辑, 不管是stop, 还是异常(很可能被捕获), 都会触发terminate.
若没有trap_exit, 收到exit消息时, 进程会直接被杀, 不会触发terminate.
若有trap_exit, 可以保证terminate被调用.
因为结果很合乎直觉, 就不看源码深究了.