smedegaard

smedegaard

Graceful shutdown of supervised GenServer

I’ve got an app that connects to a number of TCP sockets using Ranch.

The supervision tree looks like this:

|-------|
|  APP  |
|-------|
   |
   ▼
|---------------------|
| Manager(Supervisor) |
|---------------------|
  |        |        |                   
  ▼        ▼        ▼                    
|-------||-------||----------------|              
| Sup 1 || Sup 2 ||     Sup N      |               
|-------||-------||----------------|
                    |            |
                    ▼            ▼
             |------------|  |-------------|
             | TCP Reader |  | Other Child |
             |------------|  |-------------|

For each connection I start a GenServer in a supervisor… The GenServer init\1 calls connect\1.

  def connect(config) do
    case :ranch_tcp.connect(config.ip, config.port, []) do
      {:ok, socket} ->
        {:ok, socket}

      error = {:error, :econnrefused} ->
        Logger.error("Connection refused to #{inspect(config)}. Shutting down Reader")
        error

      error = {:error, _error} ->
        Logger.error(
          "CTC Socket failed to connect to #{inspect(config)}. Shutting down Reader"
        )

        error
    end
  end

What I want to do is let the Supervisor and its children die gracefully if :ranch_tcp.connect\2 returns {:error, :econnrefused}

The GenServer init looks like this

  @impl true
  def init(config) do
    case connect(config) do
      {:ok, socket} ->
#nice, start reading
        {:ok, %{:config => config, socket: socket}} 

      {:error, :econnrefused} ->
#too bad. Close self and Supervisor gracefully
        exit(:normal)

      {:error, error} ->
#What the what! Try again X times
        {:stop, error}
    end
  end

Observed behaviour

If the Genserver init\1 does not return {:ok, state} the error is propagated all the way to the Application, and it quits.

Starting ctc socket app

12:25:25.694 [error] Connection refused to %Server{ctc_name: :down_tcp_server, ip: {127, 0, 0, 1}, port: 5555, recv_buffer: 0}. Shutting down CtcReader

12:25:25.697 [info]  Application ctc_socket exited: CtcSocket.Application.start(:normal, []) returned an error: shutdown: failed to start child: Supervision.Manager
    ** (EXIT) shutdown: failed to start child: :down_tcp_server_supervisor
        ** (EXIT) shutdown: failed to start child: :down_tcp_server_reader
            ** (EXIT) normal
** (Mix) Could not start application ctc_socket: CtcSocket.Application.start(:normal, []) returned an error: shutdown: failed to start child: Supervision.Manager
    ** (EXIT) shutdown: failed to start child: :down_tcp_server_supervisor
        ** (EXIT) shutdown: failed to start child: :down_tcp_server_reader
            ** (EXIT) normal

Marked As Solved

smedegaard

smedegaard

I read that having a GenServer monitor the shutdown of other GenServers is a common pattern.

But it seemed strange to me to have a GenServer to help my Supervisor supervising…

I ended up passing the Supervisor’s pid as a init arg to the Genservers and if I got {:error, :econnrefused} when trying to connect to the TCP socket, I close the Supervisor with Supervisor.stop\1

 @impl true
  def handle_info(:connect, state = %{:config => config, :supervisor => supervisor}) do
    IO.puts("connecting")

    case connect(config) do
      {:ok, socket} ->
        Process.send_after(self(), :read_data, 1)
        new_state = Map.put(state, :socket, socket)
        {:noreply, new_state}

      {:error, :econnrefused} ->
        :ok = Supervisor.stop(supervisor)
        {:noreply, state}
    end
  end

On all other errors I let it crash :exclamation:

Feel free to suggest better patterns to this :+1:

Also Liked

amnu3387

amnu3387

You’ll want to return either {:stop, :normal} or :ignore from the init so that the Supervisor ignores the child otherwise it considers it to be crashing. I think in this case :ignore describes better its functionality. I think if you do {:stop, :normal} you’ll also have to change the GenServer restart strategy to be :transient (meaning it will only be restarted by the Supervisor in case it exits with something else than :normal or through :shutdown, or shutdown tuple, by using use GenServer, restart: :transient).

Where Next?

Popular in Questions Top

tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
Werner
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”: 14:57:30.512 [warn] ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
Kagamiiiii
Student & New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
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
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
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
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

We're in Beta

About us Mission Statement