Преамбула
В первой части данной статьи мы рассмотрели механизм связей между процессами и процесс распространения ошибок. Сегодя давайте рассмотрим один случай, который не был освещен в предыдущей части – постреляем по процессам сигналом 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.
