Search code examples
erlangerlang-shell

Why can you create multiple monitor references to the same process in Erlang?


Here is an example trace where I'm able to call erlang:monitor/2 on the same Pid:

1> Loop = fun F() -> F() end.
#Fun<erl_eval.30.99386804>
2> Pid = spawn(Loop).
<0.71.0>
3> erlang:monitor(process, Pid).
#Ref<0.2485499597.1470627842.126937>
4> erlang:monitor(process, Pid).
#Ref<0.2485499597.1470627842.126942>
5> erlang:monitor(process, Pid).
#Ref<0.2485499597.1470627842.126947>

The expressions returned by instruction #4 and #5 are different than #3, meaning that it is possible to create multiple monitor references between the current process and Pid. Is there a practical case where you would need or use multiple monitor references to the same process?

I would expect this to return the same reference (returning a new one would perhaps imply that the old one had failed/crashed), following the same logic that exists for link/1.


Solution

  • Imagine you use third party library which does this (basically what OTP *:call/* functions does):

    call(Pid, Request) ->
        call(Pid, Request, ?DEFAULT_TIMEOUT).
    
    call(Pid, Request, Timeout) ->
        MRef = erlang:monitor(process, Pid),
        Pid ! {call, self(), MRef, Request},
        receive
          {answer, MRef, Result} ->
            erlang:demonitor(Mref, [flush]),
            {ok, Result};
          {'DOWN', MRef, _, _, Info} ->
            {error, Info}
        after Timeout ->
            erlang:demonitor(MRef, [flush]),
            {error, timeout}
        end.
    

    and then you use it in your code where you would monitor the same process Pid and then call function call/2,3.

    my_fun1(Service) ->
        MRef = erlang:monitor(process, Service),
        ok = check_if_service_runs(MRef),
        my_fun2(Service),
        mind_my_stuf(),
        ok = check_if_service_runs(MRef),
        erlang:demonitor(MRef, [flush]),
        return_some_result().
    
    check_if_service_runs(MRef) ->
        receive
          {'DOWN', MRef, _, _, Info} -> {down, Info}
        after 0 -> ok
        end.
    
    my_fun2(S) -> my_fun3(S).
    
    % and a many layers of other stuff and modules
    my_fun3(S) -> call(S, hello).
    

    What a nasty surprise it would be if erlang:monitor/2,3 would always return the same reference and if erlang:demonitor/1,2 would remove your previous monitor. It would be a source of ugly and unsolvable bugs. You should start to think that there are libraries, other processes, your code is part of a huge system and Erlang was made by experienced people who thought it through. Maintainability is key here.