Let's say we have a PID1 from somewhere:
iex(1)> pid = :c.pid(0, 124, 0)
#PID<0.124.0>
And we want to use it as a GenServer reference:
iex(2)> GenServer.call(pid, :do_something)
** (exit) exited in: GenServer.call(#PID<0.124.0>, :do_something, 5000)
** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
(elixir) lib/gen_server.ex:1009: GenServer.call/3
Oops, our current process crashes. To prevent this, we could check if the PID actually refers to a process first:
iex(3)> Process.alive?(pid) && GenServer.call(pid, :do_something) || :error
:error
However, this is a classic time-of-check to
time-of-use race
condition because it's possible for the process to die after the
Process.alive?
check but before the GenServer call, causing it to fail
nevertheless.
Easier to ask forgiveness than to get permission
To mitigate this possibility, we can follow the EAFP principle, which goes: “It is easier to ask forgiveness than it is to get permission.”
Instead of first checking if the process is alive (getting permission), we should directly go ahead and make the GenServer call, but explicitly handle the failing scenario (asking forgiveness):
iex(4)> try do
...(4)> GenServer.call(pid, :do_something)
...(4)> catch
...(4)> :exit, {:noproc, _} -> :error
...(4)> end
:error
The two argument version of catch
is a special form which allows you to catch
values of any kind, including exits
(documentation).
Caveats
EAFP is commonly associated with Python, which defines the term in the glossary section of its documentation. In the Erlang/OTP ecosystem (which Elixir is a part of), there's a related philosophy of fault tolerance through expecting that errors will happen and being able to recover from them to a known good state through the supervison tree.
In light of that, it might not actually be necessary to handle a process exit explicitly like the example above—if your process is properly supervised, you could just let it crash. This is especially true if you don't have any reasonable action to take after handling the error.
-
In these examples above we are using a PID directly, but everything still applies even with named processes or via tuples. ↩︎