Supervisor without any children

I was looking over the source code for plug_cowboy and saw that in the Plug.Cowboy.start/2 they return a supervisor without any children Supervisor.start_link([], strategy: :one_for_one). What’s the reasoning for doing that?

I’m somewhat new to Elixir, so really interested in learning.

Thanks!

2 Likes

I’d say the children are started dynamically later. (without knowing about the inner workings of cowboy)

Oh, but wouldn’t that require a DynamicSupervisor?

Not really. You can add children dynamically to “regular” supervisors as well. The thing is that if you add them to “regular” one it is assumed that these will live forever rather than being temporary.

4 Likes

Thank you @hauleth and also thanks to you @Sebb for clearing that up. I’ll investigate the code further and see if I can find out where children are added.

Thank you for the clarification @hauleth
I’ve been wondering that for a while.

I think it is worth mentioning this in the documentation.

After looking at the source code this is my impression, I could be off on some or all aspects but I think this is generally correct.

Plug.Cowboy provides Plug.Cowboy.child_spec/1. When you add Plug.Cowboy to your supervision tree it starts a listener. Here’s some informative documentation and example from the library, found in lib/plug/cowboy/drainer.ex

defmodule Plug.Cowboy.Drainer do
  @moduledoc """
  Process to drain cowboy connections at shutdown.

  When starting `Plug.Cowboy` in a supervision tree, it will create a listener that receives
  requests and creates a connection process to handle that request. During shutdown, a
  `Plug.Cowboy` process will immediately exit, closing the listener and any open connections
  that are still being served. However, in most cases, it is desirable to allow connections
  to complete before shutting down.

  This module provides a process that during shutdown will close listeners and wait
  for connections to complete. It should be placed after other supervised processes that
  handle cowboy connections.

  ...

  ## Examples
      # In your application
      def start(_type, _args) do
        children = [
          {Plug.Cowboy, scheme: :http, plug: MyApp, options: [port: 4040]},
          {Plug.Cowboy, scheme: :https, plug: MyApp, options: [port: 4041]},
          {Plug.Cowboy.Drainer, refs: [MyApp.HTTP, MyApp.HTTPS]}
        ]
        opts = [strategy: :one_for_one, name: MyApp.Supervisor]
        Supervisor.start_link(children, opts)
      end
  """

This seems to cover all or most of the supervision and process generation that Plug.Cowboy provides, which happens inside your own app’s supervision tree (in most cases through Phoenix.Endpoint, probably). It doesn’t explain Plug.Cowboy’s childless supervisor. If we look at the Plug.Cowboy.start/2 callback where the supervisor is defined:

  require Logger

  @doc false
  def start(_type, _args) do
    Logger.add_translator({Plug.Cowboy.Translator, :translate})
    Supervisor.start_link([], strategy: :one_for_one)
  end

Plug.Cowboy is using the start callback to hook into the application lifecycle and add functionality to Logger. From what I can see, this is the sole reason for starting the Plug.Cowboy application. The Supervisor satisfies the application behaviour’s expected return values from start/2.

I scanned the source files (there are only 5 and they’re mostly short, thankfully :slightly_smiling_face: ) and didn’t see a reference to starting processes under this supervisor. It doesn’t have any name registration either, which would make that unnecessarily hard to achieve.

4 Likes

Thank you for your thorough explanation.

I also went looking through the source code and didn’t seem to find indications of children ever being added to the supervisor.

So what you are saying is that it’s just there to fulfill the expected return of the start/2 callback?

1 Like

That’s what I think. I have seen what looks like the same pattern in other libraries, but I can’t remember specifics off the top of my head

Actually it was LiveView where I last saw something similar. The TODO comment makes it explicit about the intent

1 Like

My comment was just about the fact that you do not need DynamicSupervisor for dynamic children list. I think that @msimonborg has the correct answer why the supervisor is started with empty children list, it is just for the configuration, not runtime stuff.

1 Like

This is correct. Great spelunking @msimonborg!

3 Likes

Thank you @josevalim !