larshei

larshei

Trapping Exits / Gracefully shutting down GenServers

I am currently scratching my head, because I am already trapping exits at one point, but I am not able to get it to work at another point.

Working Setup

In a project, I have the following setup:

  1. DynamicSupervisor :my_supervisor as part of the applications Supervision Tree
  2. A GenServer module MyServer.

:my_process is started using MyServer.start(), which looks like this:

  def start(name) do
    Logger.debug "#{__MODULE__} - #{name} starting"

    DynamicSupervisor.start_child(
      :my_supervisor,
      {
        __MODULE__,
        name
      }
    )
  end

so the Process should be attached to the Supervisor.
Then, in the MyServer.init(), exits are trapped:

  ...
  Process.flag(:trap_exit, true)
  ...

and a handle_info for handle :EXIT signals:

  def handle_info({:EXIT, _pid, reason}, state) do
    Logger.warn("#{state.fleet_name} - Stopped with reason #{inspect reason}")
    {:stop, reason, state}
  end

This all works as expected. If I run e.g. :init.stop(), the Logger warning is printed.

Non-working Setup

The setup is almost the same:

  1. DynamicSupervisor :tracker_supervisor as part of the applications Supervision Tree
  2. A GenServer module TrackerServer in a separate library

In the library:

defmodule MyLibrary.TrackerServer do
  def start_link(args) do
    GenServer.start_link(
      __MODULE__,
      args,
      name: args[:name] || __MODULE__
    )
  end

  def init(args) do
    ...
    Process.flag(:trap_exit, true)
    ...
    {:ok, initial_state}
  end

  def handle_info({:EXIT, _from, reason}, state) do
    Logger.warn("Tracking #{state.name} - Stopped with reason #{inspect reason}")
  end
end

In my project, i wrapped the GenServer.start_link() in a start() function:

  def start(name) do
    Logger.debug "#{name} - Starting Tracking"

    result = DynamicSupervisor.start_child(
      :tracker_supervisor,
      {
        MyLibrary.TrackerServer,
        [
          name: {:via, Registry, {MyRegistry, name}},
          ...
        ]
      }
    )
  end

The two examples are not unrelated.

The first example MyServer is running in my application and in its init function, the TrackerServer Process is also started. MyServer forwards some data to TrackerServer, but there is no real dependency between those two, e.g. when the TrackingServer crashes and does not exist, it is not a problem. It should be restarted by the supervisor and things should go back to normal afterwards. Tracking is mostly for statistics and missing some information here is not critical.

However, I would like to persist the state on :EXIT of the TrackerServer to actually keep what was tracked so far. But it seems the Process :EXIT handle info is never called.

I do not understand why one version works and the other does not :thinking: What am I missing here? :confused:

Marked As Solved

lud

lud

Is your process linked to another process that is not the supervisor ?

If you save the following code to an .exs file and execute it you will see that the handle_info/2 function is not called, because exit messages from the parent (in that case, the supervisor) are handled by the GenServer module itself, and not your code. But GenServer will call your terminate/2 callback.

defmodule Child do
  require Logger
  use GenServer

  def start_link(arg) do
    GenServer.start_link(__MODULE__, arg)
  end

  def init(arg) do
    Process.flag(:trap_exit, true)
    {:ok, arg}
  end

  def handle_info({:EXIT, _pid, reason}, state) do
    Logger.warn("#{inspect(reason)} in handle_info")
    {:stop, reason, state}
  end

  def terminate(reason, _state) do
    Logger.warn("#{inspect(reason)} in terminate")
  end
end

{:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one)

{:ok, _pid} = DynamicSupervisor.start_child(sup, {Child, "a"})

Supervisor.stop(sup)

So, when :init.stop() is called, is it possible that the exit message that you receive is from another worker or something?

Also Liked

larshei

larshei

Oh you are right!

I can see that I once knew this, because the working implementation actually has the terminate callback implemented (which calls the same functions as the :EXIT handle_info) but I forgot about this :sweat_smile:

Thanks!

Where Next?

Popular in Questions Top

Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
gazoon
I want to know absolute current module path. In python i could do that: os.path.abspath(__file__) Does elixir have anything similar?
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New

We're in Beta

About us Mission Statement