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.