Статьи

Erlang: связывание процессов

Пусть это не удастся, это одна из техник проектирования Erlang: учитывая, что процессы дешевы и легко доступны, мы можем создать один из них, чтобы справиться с каждой потенциально неудачной операцией и позволить им аварийно завершить работу в сложных условиях.

Благодаря этой технике код для успешного пути и для управления ошибками обычно можно разделить на разные процессы, как мы делаем с конструкциями try / catch на императивных языках.
Конечно, предполагается, что при сбое процесса существует какой-то другой код, который позаботится об этом. Erlang предоставляет некоторые примитивы, которые помогают отлавливать сбои в нужном месте, регистрировать его и изолировать сбои от остальной системы.

Наборы ссылок

Одной из возможностей обработки ошибок в Erlang является связывание процессов, двунаправленная связь, объявленная между процессами.

По сути, вместо того, чтобы порождать новый процесс и позволить ему идти своим путем:

spawn(my_module, function_name, [Arg]).

Вы можете запустить тот же процесс, одновременно связав его с текущим:

spawn_link(my_module, function_name, [Arg]).

Когда два процесса связаны, событие выхода одного из них будет распространяться на другой. Эта конструкция облегчает очистку, как если бы процессы всегда выполнялись совместно, один из них может привести к аварийному завершению работы другого (так что в конечном итоге кто-то другой сможет перезапустить их все с чистого листа).

События выхода являются транзитивными, поэтому процесс B сбой из-за связанных A, процессы, связанные с B, получат событие по очереди, и так далее, в цепочке сбоев:

A -> B -> C1
  `-> C2

Конечно, вы совсем не обязаны связывать все свои процессы.

Интересно, однако, что процесс может перехватывать выходы , превращая событие выхода из связанного процесса в сообщение, которое может быть получено и обработано. Это особенно полезно при создании серверов с дочерними процессами для каждого клиента:

  1. сервер начинает перехватывать выходы.
  2. Сервер порождает новые связанные дочерние процессы при получении запроса.
  3. В случае сбоя сервера все дочерние процессы будут связаны с ним и будут остановлены.
  4. Если ребенок падает , сервер получает сообщение, чтобы обработать это изящно; сервер не выходит.

Хватит теории связывания процессов! Давайте напишем некоторый код.

Образец кода

Давайте определим, что мы хотим от наших подпрограмм родительских (сервер) и дочерних (процессы, которые обрабатывают отдельные запросы).

В случае успеха мы говорим серверу делить его константу (42) на выбранное нами число. Затем мы хотим получить кортеж результата, содержащий правильное число.

happy_path_test() ->
  Server = spawn(fail_07, parent, []),
  Server ! {divide, 2, self()},
  receive
  {result, Result} -> Result
  end,
  ?assertEqual(21, Result).

Случай неудачи пытается разделить на ноль. После небольшого ожидания ответа мы понимаем, что произошла ошибка; тем не менее, сервер должен оставаться активным, поскольку произошел сбой только дочернего процесса.

divide_by_zero_test() ->
  Server = spawn(fail_07, parent, []),
  Server ! {divide, 0, self()},
  Status = receive
  {result, _} -> ok
  after 2000 -> error
  end,
  ?assertEqual(error, Status),
  Server ! {ping, self()},
  receive
  {pong, Server} -> ok
  end.

Так как же нам построить родителя? Сначала мы объявляем, что хотим перехватить выходы, а затем входим в основной цикл.

parent() ->
  process_flag(trap_exit, true),
  parent_loop().
In the loop, we receive possible messages from clients, or the 'EXIT' tuple.
parent_loop() ->
  receive
  {'EXIT', _Pid, {ErrorCode, _}} -> ErrorCode;
  {divide, Divisor, AnswerTo} -> spawn_link(fail_07, child, [Divisor, AnswerTo]);
  {ping, AnswerTo} -> AnswerTo ! {pong, self()}
  end,
  parent_loop().

Таким образом, мы можем записать ошибку (не показана), если получим «EXIT»; породить нового связанного ребенка, если мы получим разделение , и немедленно ответить на пинг .

Ребенок имитирует деление как дорогостоящую операцию. Обратите внимание, что в нем нет обработки ошибок: исключения во время выполнения достигнут родительского процесса в значениях, содержащихся в кортеже EXIT.

child(Divisor, AnswerTo) ->
  timer:sleep(1000),
  Result = 42 div Divisor,  % integer division
  AnswerTo ! {result, Result}.

Выводы

Связывание процессов — один из способов справиться со сбоями, с более широкими возможностями, чем позволить детям потерпеть неудачу самостоятельно. Двунаправленные отношения связывают родительские и дочерние процессы вместе, в то время как некоторые из них могут решить заманить в ловушку существование и пережить аварии своих соседей.
В следующий раз мы увидим другой способ отлова с помощью примитивов мониторинга Erlang.

Код, содержащийся в этой статье , доступен на Github .