Пусть это не удастся, это одна из техник проектирования 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
Конечно, вы совсем не обязаны связывать все свои процессы.
Интересно, однако, что процесс может перехватывать выходы , превращая событие выхода из связанного процесса в сообщение, которое может быть получено и обработано. Это особенно полезно при создании серверов с дочерними процессами для каждого клиента:
- сервер начинает перехватывать выходы.
- Сервер порождает новые связанные дочерние процессы при получении запроса.
- В случае сбоя сервера все дочерние процессы будут связаны с ним и будут остановлены.
- Если ребенок падает , сервер получает сообщение, чтобы обработать это изящно; сервер не выходит.
Хватит теории связывания процессов! Давайте напишем некоторый код.
Образец кода
Давайте определим, что мы хотим от наших подпрограмм родительских (сервер) и дочерних (процессы, которые обрабатывают отдельные запросы).
В случае успеха мы говорим серверу делить его константу (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 .