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

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
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
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
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
Phillipp
Hey, I have a NanoPi-M3 and try to install Elixir on their Ubuntu image. I followed the Raspberry Pi installation instructions from the ...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
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

Other popular topics Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
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
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
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
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
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
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

We're in Beta

About us Mission Statement