Best way to get a pid for a supervisors child

I’m deploying an app at fly.io that can shut down when idle, this post from Chris McCord shows how to do it, but in the app I’m using bandit and thousand island, not cowboy and ranch, so I asked @mtrudel if thousand island supported introspection and he implemented a way to get the connection pids from a server, which was awesomely nice of him.

Anyway, my solution uses the exact same structure as the Chris McCord one but replacing the use of ranch with Bandit and Thousand Island, the problem is that I don’t see anything wrong and it works perfectly, but maybe some of you know a more elegant way to fetch the pid needed, here’s the code:

 defp shutdown_when_inactive(every_ms) do
    Process.sleep(every_ms)

    pid =
      Supervisor.which_children(AppWeb.Endpoint)
      |> Enum.find(fn c ->
        case c do
          {{AppWeb.Endpoint, :http}, _pid, :worker, [Bandit]} -> true
          _ -> false
        end
      end)
      |> elem(1)

    {:ok, connections} = ThousandIsland.connection_pids(pid)

    if connections == [] do
      System.stop(0)
    else
      shutdown_when_inactive(every_ms)
    end
  end

Thanks

1 Like

Great to hear you got this working!

As you’ve figured out, once you get a handle on the root Bandit process (which is actually just a Thousand Island server process), you can get the list of current connection processes for it via the ThousandIsland.connection_pids/1 function added in Thousand Island 0.5.17. That part is pretty straightforward.

This leaves the problem of how to get the Bandit/Thousand Island pid from an Endpoint process (accessible via id as the Endpoint’s module name). Pulling out specific children of a supervisor (by id or otherwise) is always as awkward as you’ve done here; there’s not really an easier way. There’s a million ways to golf this sort of thing down to something more elegant but I’ve never really seen an approach I like; curious if anyone else has a better approach. FWIW I use a pattern more or less like you do here within Thousand Island.

3 Likes

Yeah, your approach is more elegant than mine, similar idea, cleaner execution, still wondering if someone has a different approach.

Thanks again

2 Likes

Thanks for sharing!

One thing thou, I’m not seeing any {{AppWeb.Endpoint, :http}, _pid, :worker, [Bandit]}, only {{AppWeb.Endpoint, :http}, _pid, :supervisor, [Bandit]}.

Is it safe to match :supervisor?

:worker or :supervisor at the place you indicate is a record of the type field from the child spec (see Supervisor — Elixir v1.14.3). The value of this field isn’t super material to you one way or the other; your match can safely ignore it.

(As background, Bandit’s behaviour in this regard had some minor chages with the 0.6.11 release that moved that field from :worker to :supervisor. It’s a purely internal change that you don’t really need to care about).

1 Like

Cool, thanks!

For future reference, here’s the snippet using everything I learned today :stuck_out_tongue:

 defp shutdown_when_inactive(every_ms) do
    Process.sleep(every_ms)

    {_, pid, _, _} =
      AppWeb.Endpoint
      |> Supervisor.which_children()
      |> Enum.find(&Kernel.match?({{AppWeb.Endpoint, :http}, _pid, _type, [Bandit]}, &1))

    {:ok, connections} = ThousandIsland.connection_pids(pid)

    if connections == [] do
      System.stop(0)
    else
      shutdown_when_inactive(every_ms)
    end
  end
1 Like