Exceptions And Errors In Erlang
What does exception look like?
-module(shop).
-export([cost/1]).
cost(apple) -> 2;
cost(orange) -> 3.
This is what happened when we ran it:
1> shop:cost(apple).
2
2> shop:cost(pear).
** exception error: no function clause matching shop:cost(pear) (shop.erl, line 4)
Raising an Exception:
exit(Why)
This is used when you really want to terminate the current process.
If this exception is not caught, the message {’EXIT’,Pid,Why}
will be
broadcast to all processes that are linked to the current process.
throw(Why)
This is used to throw an exception that a caller might want to catch. In this case we document that our function might throw this exception.
erlang:error(Why)
This is used for denoting “crashing errors.” That is, something rather nasty has happened that callers are not really expected to handle. This is on par with internally generated errors.
try...catch Syntax
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
Example:
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.
Run it:
1> try_test:demo1().
[{1,normal,a},
{2,caught,thrown,a},
{3,caught,exited,a},
{4,normal,{'EXIT',a}},
{5,caught,error,a}]
The other way to trap an exception is to use the primitive catch. When you catch an exception, it is converted into a tuple that describes the error.
demo2() ->
[{I, (catch generate_exception(I))} || I <- [1,2,3,4,5]].
Run it:
2> try_test:demo2().
[{1,a},
{2,a},
{3,{'EXIT',a}},
{4,{'EXIT',a}}, {5,{'EXIT',{a,[{try_test,generate_exception,1},
{try_test,'-demo2/0-fun-0-',1},
{lists,map,2},
{lists,map,2},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_loop,3}]}}}]
Catching Every Possible Exception
try Expr catch
_:_ -> ... Code to handle all exceptions ...
end
(End of Exception)
Errors in Concurrent Programs
Error handling in concurrent Erlang programs is based on the idea of remote detection and handling of errors. Instead of handling an error in the process where the error occurs, we let the process die and correct the error in some other process.
The Erlang philosophy for building fault-tolerant software can be summed up in two easy-to-remember phrases: “Let some other process fix the error” and “Let it crash.”
Linking Processes
Processes can be linked. If the two processes A and B are linked and A terminates for any reason, an error signal will be sent to B and the other way around.
Monitors are similar to links but are one-directional. If A monitors B and if B terminates for any reason, a “down” message will be sent to A but not the other way around.
If the receiver hasn’t taken any special steps, the exit signal will cause it, too, to exit. However, a process can ask to trap these exit signals. When a process is in this state, it is called a system process.
An on_exit
Handler
on_exit(Pid, Fun) ->
spawn(fun() ->
process_flag(trap_exit, true),
link(Pid),
receive
{'EXIT', Pid, Why} ->
Fun(Why)
end
end).
In line 3, the statement process_flag(trap_exit, true) turns the spawned process into a system process. link(Pid) (line 4) links the newly spawned process to Pid. Finally, when the process dies, an exit signal is received (line 6) and processed (line 7).
Test it:
1> F = fun() -> receive
X -> list_to_atom(X)
end
end.
#Fun<erl_eval.20.69967518>
% We’ll spawn this:
2> Pid = spawn(F).
<0.61.0>
% And we’ll set up an on_exit handler to monitor it:
3> lib_misc:on_exit(Pid,
fun(Why) ->
io:format(" ~p died with:~p~n",[Pid, Why])
end).
<0.63.0>
%% If we send an atom to Pid, the process will die (because it tries to
%% evaluate list_to_atom of a nonlist), and the on_exit handler will be called:
4> Pid ! hello.
hello
<0.61.0> died with:{badarg,[{erlang,list_to_atom,[hello]}]}
Programming Idioms for Trapping Exits
Idiom 1: I Don’t Care If a Process I Create Crashes
Pid = spawn(fun() -> ... end)
Idiom 2: I Want to Die If a Process I Create Crashes
Pid = spawn_link(fun() -> ... end)
Idiom 3: I Want to Handle Errors If a Process I Create Crashes
...
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
A Keep-Alive Process
keep_alive(Name, Fun) ->
register(Name, Pid = spawn(Fun)),
on_exit(Pid, fun(_Why) -> keep_alive(Name, Fun) end).
This makes a registered process called Name that evaluates spawn(Fun). If the process dies for any reason, then it is restarted.
There is a possibility the process dies in the gap between these two statements.
If the process dies before on_exit gets evaluated, then no link will be created,
and the on_exit process will not work as you ex- pected. This could happen if
two programs try to evaluate keep_alive at the same time and with the same
value of Name. This is called a race condition
.