Keeping a process alive just to link other processes

Viewed 66

In Programming Erlang by Joe Armstrong, Chapter 12, "Making a Set of Processes That All Die Together", the following code is given:

% (Some variables are renamed and comments added for extra clarity)

start(WorkerFuns) ->
    spawn(fun() ->
        % Parent process
        [spawn_link(WorkerFun) || WorkerFun <- WorkerFuns],
        receive
            after infinity -> true
        end
    end).

The resulting processes are linked as such:

         +- parent -+
        /      |     \
       /       |      \
worker1     worker2 .. workerN

If a worker crashes, then the parent crashes, and then the remaining workers crash as well. However, if all of the workers exit normally, then the parent process lives forever, albeit in a suspended state.

While Erlang processes are supposed to be cheap, if start/1 is called many times in a long-running service, one process—the parent—appears to be "leaked" every time all workers exit normally.

Is this ever a problem in practice? And is the extra code to properly account for when all workers exit normally (see below), worth it?

start(WorkerFuns) ->
    spawn(fun() ->
        % Parent process
        process_flag(trap_exit, true),
        [spawn_link(WorkerFun) || WorkerFun <- WorkerFuns],
        parent_loop(length(WorkerFuns))
    end).

parent_loop(0) ->
    % All workers exited normally
    true;
parent_loop(RemainingWorkers) ->
    receive
        {'EXIT', _WorkerPid, normal} ->
            parent_loop(RemainingWorkers - 1);
        {'EXIT', _WorkerPid, CrashReason} ->
            exit(CrashReason)
    end.
1 Answers

Your analysis is correct. The code as given does not account for normal termination of the workers and will leave a dangling process. The space leak will be about 2 kb per invocation, so in a large system you're not likely to notice it unless you call start/1 a thousand times or more, but for a system expected to run "forever" you should definitely add the extra code.

Related