Search code examples
processerlangwait

Wait on the reply of a process Erlang


Is it possible to spawn a process p in a function funct1 of a module module1, to send a message to p in a function funct2 of module1 and to wait for a reply of p inside funct2, without having to spawn f2 that is therefore considered as self()? If so, what is the best way to implement the waiting part? You can see the code below to have an overview of what I am looking for. Thanks in advance.

-module(module1)
...
funct1(...)
->  

 Pid = spawn(module2, function3, [[my_data]]),
 ...


funct2(...)
->
  ...
  Pid ! {self(), {data1, data2}},

  % wait here for the reply from Pid
  % do something here based on the reply.

Solution

  • The Answer

    Yes.

    The Real Problem

    You are conflating three concepts:

    • Process (who is self() and what is pid())
    • Function
    • Module

    A process is a living thing. A process has its own memory space. These processes are the things making calls. This is the only identity that really matters. When you think about "who is self() in this case" you are really asking "what is the calling context?" If I spawn two instances of a process, they both might call the same function at some point in their lives -- but the context of those calls are completely different because the processes have their own lives and their own memory spaces. Just because Victor and Victoria are both jumping rope at the same time doesn't make them the same person.

    Where people get mixed up about calling context the most is when writing module interface functions. Most modules are, for the sake of simplicity, written in a way that they define just a single process. There is no rule that mandates this, but it is pretty easy to understand what a module does when it is written this way. Interface functions are exported and available for any process to call -- and they are calling in the context of the processes calling them, not in the context of a process spawned to "be an instance of that module" and run the service loop defined therein.

    There is nothing trapping a process "within" that module, though. I could write a pair of modules, one that defines the AI of a lion and another that defines the AI of a shark, and have a process essentially switch identities in the middle of its execution -- but this is almost always a really bad idea (because it gets confusing).

    Functions are just functions. That's all they are. Modules are composed of functions. There is nothing more to say than this.

    How to wait for a message

    We wait for messages using the receive construct. It matches on the received message (which will always be an Erlang term) and selects what to do based on the shape and/or content of the message.

    Read the following very carefully:

    1> Talker =
    1>   fun T() ->
    1>     receive
    1>       {tell, Pid, Message} ->
    1>         ok = io:format("~p: sending ~p message ~p~n", [self(), Pid, Message]),
    1>         Pid ! {message, Message, self()},
    1>         T();
    1>       {message, Message, From} ->
    1>         ok = io:format("~p: from ~p received message ~p~n", [self(), From, Message]),
    1>         T();
    1>       exit ->
    1>         exit(normal)
    1>     end
    1>   end.
    #Fun<erl_eval.44.87737649>
    2> {Pid1, Ref1} = spawn_monitor(Talker).
    {<0.64.0>,#Ref<0.1042362935.2208301058.9128>}
    3> {Pid2, Ref2} = spawn_monitor(Talker).
    {<0.69.0>,#Ref<0.1042362935.2208301058.9139>}
    4> Pid1 ! {tell, Pid2, "A CAPITALIZED MESSAGE! RAAAR!"}.
    <0.64.0>: sending <0.69.0> message "A CAPITALIZED MESSAGE! RAAAR!"
    {tell,<0.69.0>,"A CAPITALIZED MESSAGE! RAAAR!"}
    <0.69.0>: from <0.64.0> received message "A CAPITALIZED MESSAGE! RAAAR!"
    5> Pid2 ! {tell, Pid1, "a lower cased message..."}.
    <0.69.0>: sending <0.64.0> message "a lower cased message..."
    {tell,<0.64.0>,"a lower cased message..."}
    <0.64.0>: from <0.69.0> received message "a lower cased message..."
    6> Pid1 ! {tell, Pid1, "Sending myself a message!"}.
    <0.64.0>: sending <0.64.0> message "Sending myself a message!"
    {tell,<0.64.0>,"Sending myself a message!"}
    <0.64.0>: from <0.64.0> received message "Sending myself a message!"
    7> Pid1 ! {message, "A direct message from the shell", self()}.
    <0.64.0>: from <0.67.0> received message "A direct message from the shell"
    {message,"A direct message from the shell",<0.67.0>}
    

    A standalone example

    Now consider this escript of a ping-pong service. Notice there is only one kind of talker defined inside and it knows how to deal with target, ping and pong messages.

    #! /usr/bin/env escript
    
    -mode(compile).
    
    main([CountString]) ->
        Count = list_to_integer(CountString),
        ok = io:format("~p: Starting pingpong script. Will iterate ~p times.~n", [self(), Count]),
        P1 = spawn_link(fun talker/0),
        P2 = spawn_link(fun talker/0),
        pingpong(Count, P1, P2).
    
    
    pingpong(Count, P1, P2) when Count > 0 ->
        P1 ! {target, P2},
        P2 ! {target, P1},
        pingpong(Count - 1, P1, P2);
    pingpong(_, P1, P2) ->
        _ = erlang:send_after(1000, P1, {exit, self()}),
        _ = erlang:send_after(1000, P2, {exit, self()}),
        wait_for_exit([P1, P2]).
    
    
    wait_for_exit([]) ->
        ok = io:format("~p: All done, Returing.~n", [self()]),
        halt(0);
    wait_for_exit(Pids) ->
        receive
            {exiting, Pid} ->
                ok = io:format("~p: ~p is done.~n", [self(), Pid]),
                NewPids = lists:delete(Pid, Pids),
                wait_for_exit(NewPids)
        end.
    
    
    talker() ->
        receive
            {target, Pid} ->
                ok = io:format("~p: Sending ping to ~p~n", [self(), Pid]),
                Pid ! {ping, self()},
                talker();
            {ping, From} ->
                ok = io:format("~p: Received ping from ~p. Replying with pong.~n", [self(), From]),
                From ! pong,
                talker();
            pong ->
                ok = io:format("~p: Received pong.~n", [self()]),
                talker();
            {exit, From} ->
                ok = io:format("~p: Received exit message from ~p. Retiring.~n", [self(), From]),
                From ! {exiting, self()}
        end.
    

    There are some details there, like use of erlang:send_after/3 that are used because message sending is so fast that it will beat the speed of the calls to io:format/2 that slow down the actual talker processes and result in a weird situation where the exit messages (usually) arrive before the pings and pongs between the two talkers.

    Here is what happens when it is run:

    ceverett@changa:~/Code/erlang$ ./pingpong 2
    <0.5.0>: Starting pingpong script. Will iterate 2 times.
    <0.61.0>: Sending ping to <0.62.0>
    <0.62.0>: Sending ping to <0.61.0>
    <0.61.0>: Sending ping to <0.62.0>
    <0.62.0>: Sending ping to <0.61.0>
    <0.61.0>: Received ping from <0.62.0>. Replying with pong.
    <0.62.0>: Received ping from <0.61.0>. Replying with pong.
    <0.61.0>: Received ping from <0.62.0>. Replying with pong.
    <0.62.0>: Received ping from <0.61.0>. Replying with pong.
    <0.61.0>: Received pong.
    <0.62.0>: Received pong.
    <0.61.0>: Received pong.
    <0.62.0>: Received pong.
    <0.61.0>: Received exit message from <0.5.0>. Retiring.
    <0.62.0>: Received exit message from <0.5.0>. Retiring.
    <0.5.0>: <0.61.0> is done.
    <0.5.0>: <0.62.0> is done.
    <0.5.0>: All done, Returing.
    

    If you run it a few times (or on a busy runtime) there is a chance that some of the output will be in different order. That is just the nature of concurrency.

    If you are new to Erlang the above code might take a while to sink in. Play with that pingpong script yourself. Edit it. Make it do new things. Create a triangle of pinging processes. Spawn a random circuit of talkers that do weird things. This will make sense suddenly once you mess around with it.