Best way to name permanent tasks?

I’m trying to name a supervised, permanent task so I can find it easier. What’s the best way to go about this? I tried out this right here:

https://hexdocs.pm/elixir/1.10.3/Task.html#module-supervised-tasks

However, when I run Process.whereis(MyApplication.MyTask) instead of getting a PID back, I get nil. This is an ongoing looping task. From MyApplication.Application I also tried moving the starting of the Task via a different Supervisor call like so:

Supervisor.start_link([
  {MyTask, arg}
], strategy: :one_for_one, id: :"MyApplication.MyTask")

And that didn’t seem to work either. I don’t know what exactly the :id would do, but I thought it was worth trying to pass that along.

When starting the Task you need to provide a :name, probably that’s missing, but you haven’t shown how you actually start it, therefore I’m just guessing.

Tasks are really not the right kind of thing to have running for a long time, you should use a genserver instead.

However to your specific question, id is not the value used to set the name, you need a name option, but I’m not even sure if tasks take a name option.

3 Likes

Indeed, Task.start_link/3 does not allow for something like the :name option.

https://hexdocs.pm/elixir/v1.10/%20/Task.html

1 Like

I built gen_loop for when I want Task-like processes and need to communicate with them. (Actually I mostly packaged it, it is just :plain_fsm under the hood).

But if you just want to be able to address a Task by name, why not just call Process.register/2 at the beginning of the task ?

Process.register/2 should work. But yeah. At the point where you have a stateless datastructure receiving messages inside a task, it’s time to use gen_statem. If you prefer a genserver like interface and enforced state graph on top of gen_statem, I wrote StateServer library for this purpose.

Otoh, if you’re doing this for fun and exploration of otp I highly encourage it!!

2 Likes

This will be a production program using this, it’s not an overly complicated project but I do have some IPC going on. Mainly, my Tasks right now are talking to a GenServer and Agent to get stored entries and run some functions in a loop. I figured since I’m not storing any state with these modules, they would make the most sense being a Task. The getting started examples seem to promote that idea as well for my same use case

I guess I don’t really need to see that my Task is running, since I created my module with use Task, restart: :permanent, I should just assume it is working. Right now I’m just finishing my biggish project with Elixir so I like to remote in and verify things are working in addition to what goes in the logs :slight_smile:

@ityonemo’s mention of Process.register/2 looks like it’s the best answer for me right now. I’ll have to see what exactly gen_statem is and how it would work with this. I’m happy that right now my project is running well, but I also like to be correct, so thank you for mentioning that!

use Task, restart: :permanent

seems not right, IMO. I don’t think statefulness/not statefulness is the right way to think about whether or not you should use a GenServer vs. a Task. There are times where I have GenServer equivalents with state nil.

I would say a GenServer is supposed to be a service, and a Task is supposed to be an action. If you speak a romance language, GenServers are imperfective and Tasks are perfective. Tasks IIRC are a creation of Elixir to give OTP a sane default way to supervise simple things with strictly internal control flow, but as soon as you’re doing message passing to interface with a task, you’re in GenServer territory.

If you are having a Task that is permanent, it must somehow be responding to a message (probably a receive block?). Although that might work for now, I would suggest changing that over to a GenServer call. If for no other reason than the caller will know if the somehow the GenServer is unavailable. Even if all your call does is reply :ok and performs a {:continue, info} to actually do its thing. That will let the GenServer manage your message queue instead of doing it manually. This is good because you’ll get solid error warnings (the calling GenServer will crash), and you’ll also get backpressure management.

If your needed throughput for your service starts go get really high, you’re going to want to do process pools, and buliding out a process pool from a GenServer is a well-trodden, battle-tested, and relatively formulaic process; doing so with a Task I suspect could be challenging.

1 Like

I have basically been spawning this task to call a GenServer, which is why it didn’t make sense to use another GenServer. This Task doesn’t do any receives, it only makes calls and then does something with the data that is returned with the call. I did repurpose my task into a GenServer, and though the behavior is no different at all, except that I do loops with :timer.send_interval/2 to trigger a call to handle_info/2, it has allowed me to handle a couple casts that could come in handy at certain times.

It also allows use of the :sys module to do things like tracing or other debugging, so it’s well worth it for long running things.

1 Like