chocolatedonut

chocolatedonut

Why are module-based Supervisors recommended (outside of the tree root)?

Can you please provide an example of how and when is a module-based supervisor preferred (over starting a Supervisor via start_link/2)?

It isn’t clear to me what do “automatically” vs. “manually”, from the docs below, mean.

The difference between the two approaches is that a module-based supervisor gives you more direct control over how the supervisor is initialized. Instead of calling Supervisor.start_link/2 with a list of child specifications that are automatically initialized, we manually initialize the children by calling Supervisor.init/2 inside its c:init/1 callback.

I’ll use the Blackjack example, from @sasajuric’s To spawn or not to spawn post, where this call of Supervisor.start_link/2 doesn’t (seem to) follow the above recommendation.

For example, before this Supervisor.start_link/2 call I can still fetch children’s initial arguments from an external service – just as I can do in Supervisor.c:init/1. What is “automatic” vs. “manual” then referring to (as I presume my example isn’t it)?


Bit further down, the docs also say:

A general guideline is to use the supervisor without a callback module only at the top of your supervision tree, generally in the Application.start/2 callback. We recommend using module-based supervisors for any other supervisor in your application, so they can run as a child of another supervisor in the tree.

As shown in the Blackjack example, it seems we can still “run [a Supervisor] as a child of another supervisor in the tree”. What OTP benefits are we missing out on then?

Or has OTP changed since then, as Sasa’s article was written at Elixir v1.4.2, and the docs on module-based Supervisor at v1.4.2 doesn’t have the above-quoted recommendations.

Most Liked

sasajuric

sasajuric

Author of Elixir In Action

I didn’t write those docs, but here’s my guess:

  1. When you invoke Supervisor.start_link(children, opts), a process is started, and that process will start the children.
  2. When you invoke Supervisor.start_link(module, arg, opts), a process is started, and that process will invoke your init callback. That callback needs to return the list of children (using Supervisor.init/2).

Hope this sheds some light, though I agree that manual vs auto is somewhat confusing.

When it comes to differences between these two approaches, I regard the former as being simpler, while the latter (with the callback module) is more flexible.

The reason is that when you have the callback module, the decision making is deferred to the latest moment in time. You generate the child specs just before the processes are about to be started. This can be useful if the restarted supervisor might need to start a different set of children.

You can’t achieve such flexibility with the basic approach. Because, even if the supervisor process is restarted, the spec has already been given, so it will always start the same set of children. You’d need to restart the parent of that supervisor to make that work.

For the same reason, I also think (but I’m not sure), that a callback-based supervisor will work better with live code upgrades.

I typically don’t care about these benefits, so I use the simpler approach for all my supervisors. The same approach is taken by Elixir in Action. TBH, I can’t recall the last time I wrote a callback-based supervisor :smiley:

christhekeele

christhekeele

José and Saša do a good job of highlighting the OTP runtime differences above, being able to do things like dynamic child generation upon init.

I would also point out the general abstraction benefits of using module-backed OTP things, and not just supervisors. start_link/2 is the general (default child_spec) interface to put anything into a supervision tree. A module implementing that alone can let you change the backing OTP abstraction at will as your project evolves.

This is especially powerful if you keep all your interface functions in the implementing module, as well. For example, if you avoid scattering Supervisor.child_spec, Supervisor.start_link, and Supervisor.count_children throughout your codebase, but call out to them from the module’s interface alongside its callbacks, you can swap out Supervisor for any other OTP abstraction while only modifying a single file.

Some recent examples where I have done this: converting an Agent to a GenServer, converting a Task.Supervisor to a DynamicSupervisor, and changing a Registry to a Phoenix.Tracker.

josevalim

josevalim

Creator of Elixir

I think those are unclear words from the docs. Perhaps “explicitly” vs “implicitly” is better. Both have to call Supervisor.init, but Supervisor.start_link does it for you implicitly, the other requires you to do so explicitly.

josevalim

josevalim

Creator of Elixir

I agree with you. Unfortunately, outside of the root, the only way to rely on start_link/2 is by providing custom child specs (with the confusing start MFA). So I have found that telling people to use a module-based approach (which its default child spec) is better than hand-rolling a child spec. Thoughts?

sasajuric

sasajuric

Author of Elixir In Action

FWIW, EiA example use a dedicated module for each supervisor, it’s just not based on the callback app, so you do something like:

defmodule MySupervisor do
  def start_link, do: Supervisor.start_link(children, opts)

  def child_spec, 
    do: %{id: __MODULE__, start: {__MODULE__, :start_link, []}, type: :supervisor}
end

But I agree this is meh, so in practice I tend to write an ad-hoc helper so I can specify something like supervisor(children, opts) as a child. Perhaps it would be nice if we could get something like that out of the box?

But even with that, sometimes a dedicated supervisor module is useful (if you want to support other functions, such as adding a child). So it would be also nice if I could do something like use Supervisor, callback?: false which would inject the proper spec, without the behaviour part.

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
gonzofish
I’m currently trying to understand how to join three tables using Ecto. All the examples I’ve seen use 2, so maybe I’m just missing somet...
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
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
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
jc00ke
Expanding on this topic: https://forum.elixirforum.net/t/map-typespec-question/19217 Let’s say I have a map with required and optional k...
New

Other popular topics Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
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
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
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
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New

We're in Beta

About us Mission Statement