Обработка ошибок в Эрланге - поподробнее
От: Mamut Швеция http://dmitriid.com
Дата: 04.06.07 11:49
Оценка: 56 (5)
Перевожу/цитирую напрямую из новой книги Армстронга. Перевод на скорую руку и не все подряд, так что не обессудьте

Сначала — sequential programming

Глава 4. Исключения

(стр. 78)

Каждый раз, когда вызывается функция, происходит одна из двух вещей: функция возвращает значение или происходит что-то страшное. Это мы уже видели в предыдущих главах:
shop.erl
cost(oranges)   -> 5;
cost(newspaper) -> 8;
cost(apples)    -> 2;
cost(pears)     -> 9;
cost(milk)      -> 7.


Вот мы ее вызываем:
1> shop:cost(apples).
2
2> shop:cost(socks).
=ERROR REPORT==== 30-Oct-2006::20:45:10 ===
Error in process <0.34.0> with exit value:
       {function_clause,[{shop,cost,[socks]},
       {erl_eval,do_apply,5},
       {shell,exprs,6},
       {shell,eval_loop,3}]}


Когда мы вызвали cost(socks), функция вылетела (crashed)...

Вызов cost(socks) является бессмыслицей. Функция не может вернуть осмысленное значение, потому что стоимость носков неопределена. Поэтому система генерирует исключение (raises exception).

Мы не пытаемся исправить ошибку, потому что это невозможно. Задача решить, что делать с упавшей функцией лежит на вызывающем.

Исключения гернерируются системой, когда происходят внутренние ошибки, или генерируются вручную с помощью вызовов throw(Exception), exit(Exception). или erlang:error(Exception).

В Эрланге есть два способа перехватить исключения. Один — это обернуть вызов функции в блок try..catch. Второй — обернуть вызов функции в выражение catch

Перехват исключений


У Эрланга есть структура, похожая на try{}catch{} в Джаве, и выглядит она так:
try FuncOrExpressionSequence of
    Pattern1 [when Guard1] -> Expressions1;
    Pattern2 [when Guard2] -> Expressions2;
    ...
catch
    ExceptionType: ExPattern1 [when ExGuard1] -> ExExpressions1;
    ExceptionType: ExPattern2 [when ExGuard2] -> ExExpressions2;
    ...
after
    AfterExpressions
end


try..catch, как и любое выражение в эрланге являет собой значение. Таким образом следующая краткая запись:
try F
catch
    ...
end

эквивалентна следующему:
try F of
  Val -> Val
catch
    ...
end


Программируем с try..catch

try_test.erl
generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {'EXIT', a};
generate_exception(5) -> erlang:error(a).


demo1() ->
     [catcher(I) || I <- [1,2,3,4,5]].
catcher(N) ->
   try generate_exception(N) of
       Val -> {N, normal, Val}
   catch
       throw:X -> {N, caught, thrown, X};
       exit:X -> {N, caught, exited, X};
       error:X -> {N, caught, error, X}
   end.


Если мы запустим это дело, то получим:
> try_test:demo1().
[{1,normal,a},
 {2,caught,thrown,a},
 {3,caught,exited,a},
 {4,normal,{'EXIT',a}},
 {5,caught,error,a}]




4.6 Стиль программирования с try..catch


Код, где часто возвращаются ошибки (Code Where Error Returns Are Common)

Если у вашей функции нет "общего случая", то имеет смысл возвращать что-то в стиле {ok, Value} или {error, Reason}, но тогда вызывающий код должен что-то делать с этими значениями. Тогда у вас есть два пути. Писать так:
case f(X) of
    {ok, Val} ->
        do_some_thing_with(Val);
    {error, Why} ->
        %% ... делаем что-то с ошибкой ...
end,
...

что обрабатывает все возможные случаи. Или так:
...
{ok, Val} = f(X),
do_some_thing_with(Val);
...

что сгенерирует исключение, если f(X) вернет {error, ...}.

Код, где ошибки возможны, но редки

Обычно вам надо будет написать код, перехватывающий все ошибки. Например, так:
try my_func(X)
catch
  throw:{thisError, X} -> ...
  throw:{someOtherError, X} -> ...
end

А код, который определяет ошибки, должен содеражть соответствующие вызовы throw:
my_func(X) ->
    case ... of
       ...
       ... ->
              ... throw({thisError, ...})
       ... ->
              ... throw({someOtherError, ...})


4.7 Ловим всевозможные исключения

try Expr
catch
    _:_ -> ... код для обработки всех исключений
end


Теперь — concurrent programming

Глава 9. Ошибки в распределенных приложениях (Errors in Concurrent Programs)



9.1 Связывание процессов


Если один процесс зависит от другого, то хочется понаблюдать за здоровьем этого процесса. Для этого используется встроенная функция (BIF) link:
1.
---                        ---
|A| ---------------------- |B|
---                        ---
А перехватывает прекращение работы (A traps exits)
A связано с В


2.
---                        \-/
|A| ---------------------- |/|
---                        /-\
В умирает


3.
---          
|A| ---------   <-- {'EXIT', B, Why}
---          
Сигнал о прекращении работы отсылается в А



Обработчик on_exit


Мы хотим выполнить действие, когда процесс прекращает работу. Мы можем написать функцию, которая создаст связь с процессом (link to process) Pid. Если Pid умирает по причине Why, тогда вызывается функция Fun(Why):

lib_misc.erl
       on_exit(Pid, Fun) ->
           spawn(fun() ->
                          process_flag(trap_exit, true),
                          link(Pid),
                          receive
                              {'EXIT', Pid, Why} ->
                                  Fun(Why)
                          end
                 end).


Пример использования:
1> F = fun() ->
         receive
            X -> list_to_atom(X)
         end
       end.
#Fun<erl_eval.20.69967518>

2> Pid = spawn(F).
<0.61.0>

3> lib_misc:on_exit(Pid,
                    fun(Why) ->
                          io:format(" ~p died with:~p~n",[Pid, Why])
                    end).
  <0.63.0>

4> Pid ! hello.
hello
<0.61.0> died with:{badarg,[{erlang,list_to_atom,[hello]}]}


Детали обработки ошибок


Когда сигнал о выходе приходит в процесс, все зависит от состояния процесса и значения сигнала согласно таблице:
trap_exitсигнал о выходедействие
truekillDie:сообщить сигнал о выходе 'killed' всем связанным процессам (Die: Broadcast the exit signal killed to the link set)
trueXДобавить {’EXIT’, Pid, X} в очередь сообщений (mailbox)
falsenormalContinue:сигнал ни о чем исчезает (Continue: Do-nothing signal vanishes)
falsekillDie:сообщить сигнал о выходе 'killed' всем связанным процессам (Die: Broadcast the exit signal killed to the link set)
falseXDie:сообщить сигнал X всем связанным процессам (Die: Broadcast the exit signal X to the link set)
Идиомы о перехвате сообщений о выходе
Идиома 1. Неважно, если процесс умрет
Процесс просто создает параллельный процесс, используя spawn:
Pid = spawn(fun() -> ... end)

Если второй процесс умрет, первый продолжит работать

Идиома 2. Мы хотим умереть, если созданный нами процесс умрет
spawn_link:
Pid = spawn_link(fun() -> ... end)

Если созданный процесс умрет ненормальной смертью (crashes with a non-normal exit), то наш процесс тоже умрет


Идиома 3. Мы хотим обработать ошибки, если созданный процесс умирает
spawn_link + trap_exit
...
process_flag(trap_exit, true),
Pid = spawn_link(fun() -> ... end),
...
loop(...).
loop(State) ->
    receive
        {'EXIT', SomePid, Reason} ->
               %% do something with the error
               loop(State1);
        ...
    end


Дальше идет описание advanced trap_exits, monitors и keep_alive процессы. Но это переводить уже лениво


dmitriid.comGitHubLinkedIn
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.