Is there any reason not to use DynamicSupervisor functions on a Task.Supervisor

If I have a Task.Supervisor, is there any reason why it would be a bad idea to use functions such as DynamicSupervisor.start_child/2 to start tasks that are managed by this supervisor? Looking at the source code for the Task.Supervisor module, Task.Supervisor.start_link/1 seems to just start a DynamicSupervisor with some settings changed, so it seems there’s no reason this shouldn’t work (and indeed, it does). However, I’m not sure if this is me abusing an implementation detail which may change at any time, or if there is anything necessarily wrong with me doing this.

Of course, it’s not difficult for me to spawn a second DynamicSupervisor myself and avoid the question all together, but I figured I’d ask; it would make some code cleaner if doing this truly was no big deal.

Thanks!

1 Like

Just wanted to give this a bump. Thanks!

What’s the reason you’d want to do this? And why not use the provided Task.Supervisor functions?

1 Like

None in particular. I had a task supervisor, and wanted to spawn a more conventional childspec as well (I believe it was spawning an Agent that led me to this question; but really my question is more general). I don’t believe the provided Task.Supervisor functions let you do that.

Like I said in the OP I can always just spawn a second supervisor, but treating the Task.Supervisor as a DynamicSupervisor seemed to work, and I wasn’t sure of the repercussions.

Here’s how I see it: The fact that Task.Supervisor uses a DynamicSupervisor under the hood is an implementation detail that should not be relied on in your code. While it’s conceivable that Task.Supervisor changes its implementation at some point to use something other than DynamicSupervisor, that’s not the reason I wouldn’t do this personally – it’s just kinda poor practice.

3 Likes

If there’s a a good use case for what you’re doing, you might suggest extending Task.Supervisor to the mailing list. It might get accepted or you might get some info that is helpful.

Really what we need is one supervisor to handle everyone’s usecases! Jokes aside, I may. I saw a comment from José a while ago about DynamicSupervisor not supporting static children since that’s kind of a niche usecase, and easy enough to spin up two supervisors; I imagine this is another one of those situations. Still, if I bump into it again, I’ll definitely look into that!

I can think of one: it makes your code easier to test. Task.Supervisor implements caller tracking, something that DynamicSupervisor doesn’t do out of the box. Tracking of callers allows Ecto.Sandbox connections and mocks created via Mox to be transparently shared between your test process and its children, even in async mode. Without this mechanism, you would have to use explicit allowances to run async tests.

1 Like

Unless I’m misunderstanding you, I think you read the question backwards. Wouldn’t that be a reason to use DynamicSupervisor functions on a Task.Supervisor (putting aside the above about it being an implementation detail)?

You’re asking: “if I use DynamicSupervisor.start_child/2 and co. instead of TaskSupervisor.start_child/2 and co. on a Task.Supervisor, are there drawbacks?”

Did I misunderstand this?

If not, I’m offering you an example of a drawback, which is a reason for sticking to Task.Supervisor functions.

the functions in Task.Supervisor implement caller tracking, so if your module Foo does Task.Supervisor.start_child(Task.Supervisor, my_task), you’ll get caller tracking. See the docs and also here. What it’s good for: when testing Foo, Ecto.Sandbox connections and Mox’s mocks created in your test process are transparently made available to my_task (and btw, this mechanism is why you can use your sandboxed connections in your LiveViews during testing, if you ever wondered).

On the other hand, if your module Foo does DynamicSupervisor.start_child(Task.Supervisor, my_task), you won’t get any caller tracking. If you want your sandboxed conenctions and mocks to be available to my_task, you’ll have to use allowances. This means adding extra code to Foo that is only there to enable testing, so generally not a good thing.

So: +1 for sticking to Task.Supervisor functions, and no reason to use DynamicSupervisor functions.

Let me know if this is still unclear.

No, but I realize now I could have phrased the original better :slight_smile:

When I said “start tasks” here, I should have said start processes. In other words, not specifically a Task (I used “task” generally here; bad idea!). I was actually thinking of launching non-Task processes, such as Agents.

Regardless, I see what you’re getting at now that I see the confusion. For the case of starting a literal Task, that makes sense. Thank you!

To give a concrete example of Task.Supervisor using DynamicSupervisor being an implmentation detail and subject to change: This already happened once, because Task.Supervisor has been a part of elixir for longer than DynamicSupervisor has been and therefore its implementation had to be changed from an earlier iteration to using DynamicSupervisor at some point.

3 Likes