Okay, quite a bit to unpack here, but everything is preference and dependent on your problem space, so my suggestions may be useful or completely useless to your situation.
Brace yourself to get ~100 different opinions.
You have several options:
exit(Pid, Reason)
as you are now- place your supervisor under a
one_for_one
supervisor andterminate_child
- since OTP 24 there is a concept of ‘significant’ processes, which if any/all of them shutdown then the supervisor then shuts down too; using this you could send a message to a significant process for it to exit which would bring everything else under than tree down with it too
Ignore the supervisor, its purpose is for you to describe the dependencies your processes and the ordering that they are spun up; so rest_for_one
is particularly useful.
What you actually care about are the processes under the supervisor so have the last child started by the supervisor call back into your waiting process (or an event bus).
If there is not a good place for you to do this, add a one-shot child processes that returns ignore
to the supervisor after sending the “started” message to whatever is interested; be aware that for strategies other than one_for_one
you may get a message even though the supervisor did not restart.
Sounds like you want a pool? Plenty of applications/libraries that can handle this for you. There are a lot of race gotchas that make rolling your own a horrible experience of baptism by fire.
If you need to roll your own, I would suggest simple_one_for_one
and spawn on demand as I suspect you do not want to carry state from one workload to the next? Processes are really cheap.
If you have an expensive initialisation step then look to use persistent_term
(wrap your init’s in the gen with global:{lock,trans,...}, [node()])
on the local node only) or if you need a shared cache, then look to ets
.
Don’t, look at server_ref()
for hints on how to steer your message to the correct gen; the return to start_link
is a Pid or if you locally/globally register it you can use the atom name of the registration.
Worth noting, is do not overlook {via, RegMod, ViaName}
if you need some more interesting steering strategies. You effectively build your own process registry which can help hugely.
If you are determined to dispatch via the supervisor, then I recommend adding a helper function that takes your reference and ties it back to something in which_children
…but this is where nothing but pain lives.
Sounds like you are attempting to roll your own mapreduce-esque service? I recommend you run the supervisor only with local pids but register them either locally and dispatch messages to them using {Name, Node}
for your server_ref()
or look to using global_groups
to automatic the service discovery.
If though you really want to do this, for the start
MFA you should wrap it with erpc
to spawn and call start_link({local, ...}, ...)
on a remote node and then make server_ref()
for anything calling it {Name, Node}
.
On a related note, one interesting thing you can do here is (ab)use supervisors to create global singletons too.
Depends on how you need to access them. ets
is good as a generic “works for 99% of situations” so sure…use that.
What I like to do is create a rest_for_one
supervisor, have my gen as first child and then have that gen spawn the child supervisors along side it. You options are (ugly) read which_children
or (strongly recommended) have your process store the list in its own state. proplists
are not really a good fit, you will find the lists:key{find,...}
functions much more appropriate.