7
votes

In my Erlang/OTP application I have a one_for_all supervisor (sup) with several childs. One of the children (child1 with gen_server behaviour) should be able to send messages to the other one (child2 with supervisor behaviour). Of course, I can register it but clogging up the global scope with excess names doesn't seem to be a good idea.

Thereby the only way of making such interaction possible is to provide child1 with the pid of child2. No sooner said than done. There is supervisor:wich_children/1 call with appropriate functionality. Just passing sup's pid as the argument to chidl1, calling which_children in the child1:init, and... getting a deadlock. sup is waiting child1 to start, child1 is waiting sup for children descriptions:

init(SupPid) ->
    Descriptions = supervisor:which_children(SupPid),
    ... .

This can be fixed by the following kludge:

init(SupPid) ->
    gen_server:cast(self(), initialize),
    ... .

handle_cast(initialize, State) ->
    Descriptions = supervisor:which_children(SupPid),
    ...  % Generating new state containing desired pid
    {noreply, NewState}.

However, I wasn't satisfied by this solution.

The question is: What is the most conventional way of interacting between supervision tree members according to OTP design principles?

2
Could you explain why your solution doesn't satisfy you?evnu
If you intend to create an application, it can run only once per VM, so in the case you describe, I don't think that using locally registered process is a bad solution.Pascal
@Pascal, yes, I intend. But such registration cause not only name clashes between instances of the "application", but also increasing probability of name clashes with other process. Nevertheless, it looks like I should consider this solution.citxx
@evnu, this solution doesn't look very natural and clear. I created the new message specially for using it once at the initialization phase. This message is sent by process to itself... Occam turns in his grave (en.wikipedia.org/wiki/Occam's_razor).citxx
to avoid name clashes I usually choose the module name as process name, if I have no module name clash, there is low chance that I have it for process one.Pascal

2 Answers

5
votes

Surely you cannot ask the supervisor about its children while it has not yet started them all :)

Registering (locally, using erlang:register()) is not such a bad idea, actually. Moreover, if dealing with raw pid in child1 you should manually setup monitoring on child2 pid in order to be able to react on possible crashes etc, but being registered you just ask it straight away by name.

Without registering, you could defer notifying children until supervisor:start_link is called:

start_link() ->
    R=supervisor:start_link({local, ?SERVER}, ?MODULE, []),

    %% Here supervisor is started so you can notify its children
    R.
1
votes

It depends a lot on your business logic but if it makes sense to you to be worried about clogging up the global scope, my suggestion is that you consider the supervision strategy that you are using.

What I have seen in such cases when the two peer processes need to be associated in such a way, is that an extra controller process is created to manage this association and a simple_one_for_one sup is used to spawn one of the two peer processes (the gen_server most likely). This technique, again may be used when using process registration is a concern, such as when you will have many instances of those gen_servers running the same code.

The technique would basically consist on using a simple_one_for_one supervisor to spawn your gen_server. What happens basically is that when started (a simple_one_for_one sup) does not spawn any child processes immediately but only when you explicitly call supervisor:start_child(Sup, List).

Then your child2 initialization logic (or even your business logic at some point) could spawn the gen_server via the supervisor:start_child(Sup, List) and be fed with the child2 Pid so that they can communicate painlessly.

The extra controller process keeps a dict of the relationship between your gen_server, identified by some uniqueness but avoiding the global space clogging since this is just a local to the controller. Then you may be able to ask your controller to allocate or deallocate this relationship at any point on your logic.

The supervision tree would therefore look something like:

supervision tree

You can read code using this implementation on the tinymq project on github

If you have never used a simple_one_for_one supervisor before, the child specification may be a bit tricky, bear in mind that you would pass the child2's pid to your gen_server via the list on the supervisor:start_child(Sup, List) which gets appended to the Args one, that you specify on the child spec.

simple_one_for_one supervisors

My two cents!