ollien

ollien

What's the pitfall of calling Task.Supervisor.start_link by hand?

In the Task.Supervisor docs, it states

You can also start it by calling start_link/1 directly … But this is recommended only for scripting and should be avoided in production code. Generally speaking, processes should always be started inside supervision trees.

However, this is not elaborated on.

In an application I’m writing, my supervision tree contains some number of instances of a single GenServer. This GenServer will spawn several tasks during its lifetime whose return value I care about; they are not fire and forget. As part of the GenServer’s state, I store the pid of a Task.Supervisor that I start in GenServer.init/1. In my mind, this makes sense, because if theTask.Supervisor crashes (due to too many task failures or what have you), my GenServer crashes and its supervisor restarts it.

If, however, I were to run the Task.Supervisor and the GenServer under the same supervision tree, I foresee race conditions where the GenServer is trying to spawn a task on a currently restarting Task.Supervisor (e.g. imagine that the Task.Supervisor has crashed, and before its supervisor restarts it, the GenServer attempts to spawn a task… this will surely fail). I can imagine this becoming exacerbated as more of the GenServers send tasks to this supervisor, as it will be even easier to trip the max_restarts of the Task.Supervisor,

Why do the docs say to prefer this approach?

Marked As Solved

whatyouhide

whatyouhide

Elixir Core Team

Great question.

So, one of the reasons the docs mention that the supervised approach is preferred is shutdown. When a supervisor gets stopped (for example, by your application shutting down gracefully), it gives children a shutdown period in which they can shutdown gracefully. The shutdown happens by following the supervision tree, with the leaves shutting down first and then going up the tree.

If a GenServer under a supervisor starts a Task.Supervisor with start_link/1, then the main supervisor doesn’t know about the Task.Supervisor. When you’ll shut down the main supervisor (for example, restarting your application for a deploy), then it will shut down your GenServer gracefully, but the GenServer is not a supervisor, so it won’t wait for the Task.Supervisor to shut down gracefully either. This means that the tasks under that supervisor might not shut down gracefully, which can be an issue.

The simplest option is considering whether you do need one Task.Supervisor per GenServer. You might go around this by having a global Task.Supervisor (or a pool of them, with PartitionSupervisor maybe), and using Task.Supervisor.async/3 to spawn tasks that are still linked to the GenServer. This should keep the reciprocal crash semantics.

Alternatively, you can have a supervision structure for each GenServer that looks like this:

[ParentSupervisor (Supervisor)] ← uses strategy :rest_for_one
          /            \ 
[Task.Supervisor]  [GenServer]

With this setup and using :rest_for_one, you achieve this: if the Task.Supervisor crashes, then GenServer is stopped too (since it appears after the task supervisor in the ParentSupervisor), so you don’t have to worry about the GenServer trying to spawn tasks on a dead task supervisor.

If the GenServer crashes, the Task.Supervisor won’t need to crash. If you started your tasks with async/3, they’ll be brought down anyways. If this isn’t good, you can always change the strategy to :one_for_all.

The only issue with this approach is that the GenServer doesn’t know “how to reach” the Task.Supervisor. A practical solution to this is to do something like generate a random term (a ref for example) in the ParentSupervisor:init/1 callback, and pass it down to both children, which use it to register themselves in a global Registry or something like that.

10
Post #2

Also Liked

ollien

ollien

Thank you so much! This makes total sense to me

trisolaran

trisolaran

Thanks, but the situation we were talking about, described in the OP, was a GenServer starting a Task.Supervisor directly using start_link/1, outside of a supervision tree. So in that case, if the GenServer sends :kill signal to the Task.Supervisor, it will die right away with no terminate callback being called. At least that’s my understanding.

Where Next?

Popular in Questions Top

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
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
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
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New

Other popular topics Top

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
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
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
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
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
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
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement