Преамбула
В первой части данной статьи мы рассмотрели механизм связей между процессами и процесс распространения ошибок. Сегодя давайте рассмотрим один случай, который не был освещен в предыдущей части – постреляем по процессам сигналом kill.
Сигнал kill
Сигнал kill не перехватываемый сигнал, т.е. он всегда убивает процесс, даже, если он системный. Это используется в OTP супервизором для принудительного завершения сбойных процессов. Когда процесс получает сигнал kill, он умирает и сигналы killed рассылаются по всем его связям.
Во всех примерах будет использовать такое дерево процессов.

Рисунок 1
Давайте напишем небольшой модуль.
-module(test).
-export([start/1, proc/2]).
start(Sysproc) ->
process_flag(trap_exit, Sysproc),
io:format("Shell Pid: ~p~n", [self()]),
PidA = spawn_link(test, proc, [self(), a]),
PidB = spawn_link(test, proc, [self(), b]),
io:format("ProcA: ~p~nProcB: ~p~n", [PidA, PidB]),
exit(PidB, kill).
proc(Shell, Tag) ->
receive
after 5000 ->
Shell ! {hello_from, Tag, self()}
end.
В функции start/1 мы создаем два процесса: A и B, телом процессов служит функция proc/2, первым аргументом которой является Pid оболочки, второй Tag:atom(), который служит для удобства восприятия при анализе от какого процесса пришло сообщение. После создания вызывается функция exit/2, которая процессу B посылает сигнал kill.
Компилируем и запускаем.
(emacs@aleksio-mobile)2> test:start(false).
Shell Pid: <0.36.0>
ProcA: <0.43.0>
ProcB: <0.44.0>
** exception exit: killed
(emacs@aleksio-mobile)3> flush().
ok
(emacs@aleksio-mobile)4> self().
<0.45.0>
(emacs@aleksio-mobile)5>
Вызываем функцию start/1 (shell в данном примере не системный процесс и сигналы выхода ловить не может). После порождения процессов видим сообщение "** exception exit: killed", которое означает, что shell был убит с причиной killed. То, что shell действительно пал смертью храбрых говорит его новый Pid <0.45.0>. Схематично весь процесс можно изобразить следующим образом.

Рисунок 2
Ошибка распространяется по всем связям и все процессы умирают. Теперь слегка модифицируем код так, чтобы создаваемые процессы можно было сделать системными.
-module(test).
-export([start/1, proc/3]).
start(Sysproc) ->
process_flag(trap_exit, Sysproc),
io:format("Shell Pid: ~p~n", [self()]),
PidA = spawn_link(test, proc, [self(), a, false]),
PidB = spawn_link(test, proc, [self(), b, true]),
io:format("ProcA: ~p~nProcB: ~p~n", [PidA, PidB]),
exit(PidB, kill).
proc(Shell, Tag, Sysproc) ->
process_flag(trap_exit, Sysproc),
receive
after 5000 ->
Shell ! {hello_from, Tag, self()}
end.
(emacs@aleksio-mobile)2> test:start(false).
Shell Pid: <0.36.0>
ProcA: <0.43.0>
ProcB: <0.44.0>
** exception exit: killed
(emacs@aleksio-mobile)3> flush().
ok
(emacs@aleksio-mobile)4> self().
<0.45.0>
(emacs@aleksio-mobile)5>
Несмотря на то, что процесс B был сделан системным (двойная окружность на рисунке 3), он все равно умирает получив сигнал kill, после чего сигнал killed распространяется по связям и все процессы умирают.

Рисунок 3
Теперь давайте сделаем shell тоже системным.
(emacs@aleksio-mobile)2> test:start(true).
Shell Pid: <0.36.0>
ProcA: <0.43.0>
ProcB: <0.44.0>
true
(emacs@aleksio-mobile)3> flush().
Shell got {'EXIT',<0.44.0>,killed}
Shell got {hello_from,a,<0.43.0>}
Shell got {'EXIT',<0.43.0>,normal}
ok
(emacs@aleksio-mobile)4> self().
<0.36.0>
(emacs@aleksio-mobile)5>
Видим, что процесс B был убит, shell смог перехватить сигнал выхода от него (Shell got {'EXIT',<0.44.0>,killed}), процесс A послал сообщение и благополучно завершил работу (рисунок 4).

Рисунок 4
Осталось протестировать ещё один случай.
-module(test).
-export([start/1, proc/4]).
start(Sysproc) ->
process_flag(trap_exit, Sysproc),
io:format("Shell Pid: ~p~n", [self()]),
PidA = spawn_link(test, proc, [self(), a, true,
fun() -> ok end]),
PidB = spawn_link(test, proc, [self(), b, false,
fun() -> exit(kill) end]),
io:format("ProcA: ~p~nProcB: ~p~n", [PidA, PidB]).
proc(Shell, Tag, Sysproc, F) ->
process_flag(trap_exit, Sysproc),
F(),
receive
Msg ->
io:format("Proc ~p get msg: ~p.~n", [Tag, Msg])
after 5000 ->
Shell ! {hello_from, Tag, self()}
end.
В функцию proc был добавлен дополнительный аргумент — функция, которая будет вызываться перед блоком receive. Для процесса A функцию будет возвращать лишь значение ок, для процесса B в ней будет происходить вызов функции exit/1 с параметром kill, т.е. kill мы будем создавать внутри процесса, а не посылать сигнал снаружи. Процесс A в данном эксперименте всегда будет системным. Запускаем проверяем.
(emacs@aleksio-mobile)2> test:start(false).
Shell Pid: <0.36.0>
ProcA: <0.43.0>
ProcB: <0.44.0>
Proc a get msg: {'EXIT',<0.36.0>,killed}.
** exception exit: killed
(emacs@aleksio-mobile)3> flush().
ok
(emacs@aleksio-mobile)4> self().
<0.45.0>
(emacs@aleksio-mobile)5>
Вызываем нашу функцию start/1 (shell делаем не системным), как и ожидалось shell упал. Процесс B был убит сигналом kill, после чего сигнал был передан shell, оболочка упала, затем сигнал был пойман процессом A (рисунок 5).

Рисунок 5
Внимание вопрос! Какой сигнал был передан процессом B оболочке (на рисунке три знака вопроса). По идее должен быть killed. Давайте это проверим — сделаем shell системным.
(emacs@aleksio-mobile)2> test:start(true).
Shell Pid: <0.36.0>
ProcA: <0.43.0>
ProcB: <0.44.0>
ok
(emacs@aleksio-mobile)3> flush().
Shell got {'EXIT',<0.44.0>,kill}
Shell got {hello_from,a,<0.43.0>}
Shell got {'EXIT',<0.43.0>,normal}
ok
(emacs@aleksio-mobile)4> self().
<0.36.0>
(emacs@aleksio-mobile)5>
Оболочка получила сигнал {'EXIT',<0.44.0>,kill}, но не завершила свою работу! Готового ответа почему так у меня пока нет. Может быть кто-нибудь из читателей знает?
В следующей части мы рассмотрим механизм мониторов.
Список литературы
1. Отличная интерактивная документация.
2. ERLANG Programming by Francesco Cesarini and Simon Thompson.
3. Programming Erlang: Software for a Concurrent World by Joe Armstrong.