Search code examples
erlang

Erlang master process terminating slaves


I am learning Erlang and I am confused by the following code:

-module(killing_master).
-export([start/1, loop/1]).

start(SlavesNum) ->
    process_flag(trap_exit, true),
    [spawn_link(fun() ->
                        io:format("slave ~p~n", [N]),
                        loop(N)
                end) || N <- lists:seq(1, SlavesNum)],
    spawn_link(fun() -> process_flag(trap_exit, true), init() end),
    exit(normal).

init() ->
    receive
        {'EXIT', From, Why} -> io:format("start: (~p) exited ~p ~p~n", [self(), From, Why]);
        Catch -> io:format("~p~n", [Catch])
    end.

loop(SlaveNum) ->
    receive
        Msg -> io:format("slave #~p received ~p~n", [SlaveNum, Msg])
    end.

The behaviour I am expecting is that after the master exits all slaves should exit too (because of spawn_link), printing a message before doing so; insteand I am getting this:

1> killing_master:start(10).
slave 1
slave 2
slave 3
slave 4
slave 5
slave 6
slave 7
slave 8
slave 9
slave 10
** exception exit: normal

Solution

  • The behaviour I am expecting is that after the master exits all slaves should exit too (because of spawn_link), printing a message before doing so

    In addition, none of the slave processes are trapping exits, so they won't print a message when they receive an exit signal. Your slaves are created inside a list comprehension here:

    [spawn_link(fun() ->
                    io:format("slave ~p~n", [N]),
                    loop(N)
                end) 
    || N <- lists:seq(1, SlavesNum)],
    

    And, each slave process runs the function:

    fun() ->
        io:format("slave ~p~n", [N]),
        loop(N)                 
    end
    

    and the loop/1 function doesn't trap exits either.

    Here's some simpler code you can try:

    -module(a).
    -compile(export_all).
    
    master() ->
        process_flag(trap_exit, true),
    
        io:format("master pid: ~w~n", [self()]),
        spawn_link(fun slave/0),
    
        receive
            {'EXIT', From, Why} ->
                io:format("master(): Process (~w) exited~nExit signal from: ~w~nReason: ~w~n",
                          [self(), From, Why]);
            Any ->
                io:format("master(): non-exit message... ~w~n", [Any])
        end.
    
    slave() -> 
        process_flag(trap_exit, true),
    
        io:format("slave 1~n"),
    
        receive
            {'EXIT', From, Why} ->
                io:format("slave(): Process (~w) exited~nExit signal from: ~w~nReason: ~w~n",
                          [self(), From, Why]);
            Any ->
                io:format("slave(): non-exit message... ~w~n", [Any])
        end.
    

    In the shell:

    ~/erlang_programs% erl
    Erlang/OTP 24 [erts-12.3.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]
    Eshell V12.3.2  (abort with ^G)
    
    1> c(a).                         
    a.erl:2:2: Warning: export_all flag enabled - all functions will be exported
    %    2| -compile(export_all).
    %     |  ^
    
    {ok,a}
    
    2> Master = spawn(a, master, []).
    master pid: <0.90.0>
    <0.90.0>
    slave 1
    
    3> exit(Master, normal).         
    master(): Process (<0.90.0>) exited
    Exit signal from: <0.83.0>
    Reason: normal
    true
    slave(): Process (<0.91.0>) exited
    Exit signal from: <0.90.0>
    Reason: normal
    
    4> self().
    <0.83.0>
    

    The reason could be that the master dies even before the spawning of the slave child.

    In that case, I would think that spawn_link would return something like {nolink, new_pid}. It seems strange that there is no way to know if the link was successful. You can confirm that the master process has in fact exited with this code:

    -module(a).
    -compile(export_all).
    
    master() ->
        io:format("master pid: ~w~n", [self()]),
        spawn_link(fun slave/0).
    
    
    slave() -> 
        process_flag(trap_exit, true),
        io:format("slave 1~n"),
    
        receive
            {'EXIT', From, Why} ->
                io:format("slave(): Process (~w) exited~nExit signal from: ~w~nReason: ~w~n",
                          [self(), From, Why]);
            Any ->
                io:format("slave(): non-exit message... ~w~n", [Any])
        end.
    

    In the shell:

    > c(a).                         
    a.erl:2:2: Warning: export_all flag enabled - all functions will be exported
    %    2| -compile(export_all).
    %     |  ^
    
    {ok,a}
    
    2> Master = spawn(a, master, []).
    master pid: <0.90.0>
    <0.90.0>
    slave 1
    
    3> i().
    ...
    ...
    disk_log_server       gen_server:loop/7                       12              
    <0.80.0>              disk_log:init/2                       4185    12507    0
                          disk_log:loop/1                          7              
    <0.82.0>              erlang:apply/2                       28690    19928    0
                          shell:shell_rep/4                       18              
    <0.83.0>              erlang:apply/2                      121536    46746    0
                          c:pinfo/1                               49              
    <0.91.0>              erlang:apply/2                         233       33    0
                          a:slave/0                                2              
    Total                                                     213936   561931    0
                   
    

    The master() process pid was <0.90.0>, which isn't listed, and you can see that slave() is running in process <0.91.0>. Or, you can call:

    4> is_process_alive(Master).
    false
    

    Yet, slave() did not output any exit messages. The conclusion is that the master process exited before the slave process was ever linked to it.

    Therefore, your code has two problems:

    1. The master process is never linked to the slave processes.
    2. Even if the master process were linked to the slave processes, the slave processes are not trapping exits, so they won't print out an exit message.