I think you would also be able to implement something simple with the PartitionSupervisor coming in 1.14. You would have to do some hashing to make sure the messages are not duped on multiple server partitions.
To me, node up and new user are separate topics, so by splitting them you can have other processes subscribe to one or the other in the future without coupling them together
I put the NodeListener as its own process precisely because it has one responsibility. It reduces the load on Repo because Repo doesnât care about nodedown (at least thatâs what I took away from what you said). And if other processes may be interested in cluster membership, the NodeListener can be the hub for those events for the whole node, without needing multiple processes to be subscribing to net_kernel.monitor_nodes.
If there is an error that you expect might happen and you have a way to recover from it, then I think you handle it explicitly with pattern matching. If there is a real exception with no recovery then âlet it crashâ, and the supervisor will restart your process at a known good state.
I added this because you said
So I assumed you were only interested in :nodeup, not :nodedown. If you monitor nodes you will receive both, so I discarded the :nodedown messages. Maybe that was a wrong assumption
Thanks for enlightening me. I will dig into the PartitionSupervisor. Many tools with OTP! It seems to make powerful and lightweight apps. It is surprising itâs not more widely used, for web apps I mean.
Progressing slowly with Elixir. If I donât use state in a module but only use the messaging part, I understand I may not need a Genserver. I used a supervised Task and use a receive do loop in the module. This seems to work (you can subscribe and receive the event/data between distributed nodes). However, this looks like Iâm using Genserver with more complications and no benefit, arenât I?
# some module broadcasts an event/payload:
Phoenix.PubSub.broadcast(MyApp.PubSub, "node_up", {:new data})
# the "listener" module is started in the "children" array of the app supervisor
children = [..., {MyApp.Listener, opts},...]
defmodule MyApp.Listener
# no need "child_spec"
use Task, restart: : permanent
def start_link(opts) do
task = Task.start_link(__MODULE__, :start, [opts])
IO.inspect(task, label: "start_link")
end
def start(opts) do
do_something_with(opts)
Phoenix.PubSub.subscribe(MyApp.PubSub, "node_up")
monitor()
end
def monitor() do
receive do
{:new, payload} ->
IO.inspect(payload, label: "Node UP -> action with: ")
monitor()
end
end
end
Side note: I canât reach for Process.whereis(MyApp.Listener) == nil, however the process appears by pid under the supervision tree.
My take on this comment that I think youâre referencing was that itâs guiding you to consider not using a GenServer for bare function calls when the GenServerâs state is being ignored. The original code you posted didnât have any use for the GenServer state, nor was the GenServer receiving and responding to any pubsub messages, so it could have been written as a module and functions to allow parallel execution from any process, rather than serial execution from a single process.
Now that itâs clear you need a process to listen for and respond to changes in cluster membership, it is appropriate to use a GenServer for that. I think the way youâre using the Task as a replacement is a misunderstanding of the basis for that original comment, and a misuse of Tasks, which are really an abstraction for one-off computations. A GenServer is a general-purpose abstraction for listening to and handling messages in a loop, so itâs the right tool for the job IMO.
AFAIK you canât register a name for a Task, precisely because theyâre not meant to be used as this type of long running âreachableâ process
Yes, when I read about the Task, it seemed like an inappropriate tool, thatâs why I posted this. It was however instructive! Now it makes sense. Thanks.